diff --git a/.gitignore b/.gitignore index 9aab6a2..29389f6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ scripts/ tsconfig.tsbuildinfo AUTH_QUICK_REFERENCE.md AUTHENTICATION_TROUBLESHOOTING.md -src/app/api/debug.ts \ No newline at end of file +src/app/api/debug.ts +src/app/api/debug \ No newline at end of file diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index cee98df..ab1a5c5 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -1,387 +1,554 @@ -# Real-Time Collaborative Platform Troubleshooting Guide +# πŸš€ Real-Time Collaborative Platform - Troubleshooting & Setup Guide -This document contains solutions for common issues encountered when setting up and running the real-time collaborative platform. +## πŸ“‹ Table of Contents -## πŸ” Authentication Issues +- [Project Overview](#project-overview) +- [Development Environment Setup](#development-environment-setup) +- [Docker Compose Setup](#docker-compose-setup) +- [Supabase Configuration](#supabase-configuration) +- [Authentication System](#authentication-system) +- [Common Issues & Solutions](#common-issues--solutions) +- [Production Deployment](#production-deployment) +- [Performance Optimization](#performance-optimization) -### Issue 1: "user from dashboard page: null" +--- -**Problem**: User is not detected after successful login, dashboard shows null user. +## 🎯 Project Overview -**Root Cause**: Server-side Supabase client was not properly configured for authentication. +**Real-Time Collaborative Platform** is a modern web application built with Next.js 15, featuring: -**Solution**: +- **Real-time collaboration** with live document editing +- **Workspace management** for teams and projects +- **User authentication** via Supabase Auth +- **Subscription management** with Stripe integration +- **Collaborator management** with role-based access +- **Cross-platform compatibility** (web, mobile-responsive) -1. **Update server-side client** (`src/utils/server.ts`): - - ```typescript - import { createServerClient, type CookieOptions } from '@supabase/ssr'; - import { cookies } from 'next/headers'; - - export async function createClient() { - const cookieStore = await cookies(); - return createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - get(name: string) { - return cookieStore.get(name)?.value; - }, - set(name: string, value: string, options: CookieOptions) { - try { - cookieStore.set({ name, value, ...options }); - } catch (error) { - // Server Component context - } - }, - remove(name: string, options: CookieOptions) { - try { - cookieStore.set({ name, value: '', ...options }); - } catch (error) { - // Server Component context - } - }, - }, - } - ); - } - ``` - -2. **Update auth actions** (`src/lib/server-action/auth-action.ts`): - - ```typescript - import { createClient } from '@/utils/server'; - - export async function actionLoginUser({ email, password }) { - const supabase = await createClient(); - const response = await supabase.auth.signInWithPassword({ - email, - password, - }); - return response; - } - ``` - -3. **Update dashboard page** (`src/components/features/main/dashboard/dashboard-page.tsx`): - - ```typescript - import { createClient } from '@/utils/server'; - - const DashboardPage = async () => { - const supabase = await createClient(); - const { - data: { user }, - } = await supabase.auth.getUser(); - // ... rest of the code - }; - ``` - -### Issue 2: No redirect to dashboard after login - -**Problem**: User successfully logs in but doesn't get redirected to dashboard. - -**Root Cause**: Middleware not properly detecting sessions or auth state not updating. +### πŸ—οΈ Tech Stack -**Solution**: +- **Frontend**: Next.js 15, React 18, TypeScript +- **Backend**: Next.js API Routes, Supabase +- **Database**: PostgreSQL (via Supabase) +- **Authentication**: Supabase Auth with OAuth & Email/Password +- **Real-time**: WebSocket connections +- **Styling**: Tailwind CSS, shadcn/ui components +- **State Management**: React Context, Zustand +- **Payment**: Stripe integration +- **Deployment**: Vercel, Docker -1. **Add auth state listener** (`src/lib/providers/supabase-user-provider.tsx`): - - ```typescript - useEffect(() => { - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(async (event, session) => { - if (session?.user) { - setUser(session.user); - } else { - setUser(null); - } - }); - return () => subscription.unsubscribe(); - }, [supabase]); - ``` - -2. **Update middleware** (`src/middleware.ts`): - - ```typescript - export async function middleware(req: NextRequest) { - const supabase = await createClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (req.nextUrl.pathname.startsWith('/dashboard')) { - if (!session) { - return NextResponse.redirect(new URL('/login', req.url)); - } - } - - if (['/login', '/signup'].includes(req.nextUrl.pathname)) { - if (session) { - return NextResponse.redirect(new URL('/dashboard', req.url)); - } - } - return NextResponse.next(); - } - ``` - -## πŸ—„οΈ Database Issues - -### Issue 3: "relation 'products' does not exist" (and other tables) - -**Problem**: Database tables missing, causing build errors and runtime failures. - -**Root Cause**: Database schema not properly migrated or tables not created. +--- -**Solution**: +## πŸ› οΈ Development Environment Setup -1. **Check existing tables**: - - ```bash - psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -c "\dt" - ``` - -2. **Create missing tables manually**: - - ```sql - -- Create enum types - DO $$ BEGIN - CREATE TYPE "subscription_status" AS ENUM ('trialing', 'active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'unpaid'); - EXCEPTION WHEN duplicate_object THEN null; END $$; - - DO $$ BEGIN - CREATE TYPE "pricing_plan_interval" AS ENUM ('day', 'week', 'month', 'year'); - EXCEPTION WHEN duplicate_object THEN null; END $$; - - DO $$ BEGIN - CREATE TYPE "pricing_type" AS ENUM ('one_time', 'recurring'); - EXCEPTION WHEN duplicate_object THEN null; END $$; - - -- Create missing tables - CREATE TABLE IF NOT EXISTS "users" ( - "id" uuid PRIMARY KEY NOT NULL, - "full_name" text, - "avatar_url" text, - "billing_address" jsonb, - "payment_method" jsonb, - "email" text, - "updated_at" timestamp with time zone - ); - - CREATE TABLE IF NOT EXISTS "workspaces" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "workspaces_owner" uuid NOT NULL, - "title" text NOT NULL, - "icon_id" text NOT NULL, - "data" text NOT NULL, - "in_trash" text, - "logo" text, - "banner_url" text - ); - - -- Add other missing tables... - ``` - -3. **Fix database configuration** (`src/lib/supabase/db.ts`): - ```typescript - // Use correct database URL, not API URL - const client = postgres(process.env.NEXT_PUBLIC_DATABASE_URL as string, { max: 1 }); - ``` - -### Issue 4: JWT Secret Mismatch - -**Problem**: Authentication fails due to JWT secret mismatch between Supabase CLI and environment. +### Prerequisites -**Solution**: +- Node.js 18+ +- Docker & Docker Compose +- Git +- pnpm (recommended) or yarn -1. **Check Supabase CLI JWT secret**: +### Initial Setup - ```bash - supabase status - ``` +```bash +# Clone the repository +git clone +cd real-time-collaborative-plateform -2. **Update .env to match**: - ```env - NEXT_PUBLIC_JWT_SECRET="super-secret-jwt-token-with-at-least-32-characters-long" - ``` +# Install dependencies +pnpm install -## πŸ”§ Build Issues +# Copy environment variables +cp .env.example .env.local -### Issue 5: Build errors with missing dependencies +# Start development server +pnpm dev +``` -**Problem**: `Cannot find package '@next/bundle-analyzer'` or similar. +--- -**Solution**: +## 🐳 Docker Compose Setup -```bash -yarn add @next/bundle-analyzer -``` +### Local Development with Docker -### Issue 6: UUID import errors +Our project includes a comprehensive Docker setup for local development: -**Problem**: `uuidv4` import issues in dashboard setup. +#### 1. **Main Application Container** -**Solution**: +```yaml +# docker-compose.yml +services: + app: + build: . + ports: + - '3000:3000' + environment: + - NODE_ENV=development + volumes: + - .:/app + - /app/node_modules + depends_on: + - supabase +``` -```typescript -// Change from: -import { uuid } from 'uuidv4'; +#### 2. **Supabase Local Development** + +```yaml +supabase: + image: supabase/supabase-dev + ports: + - '54321:54321' # Supabase API + - '54322:54322' # PostgreSQL + - '54323:54323' # Studio + environment: + - POSTGRES_PASSWORD=your_password + - JWT_SECRET=your_jwt_secret + volumes: + - supabase_data:/var/lib/postgresql/data +``` + +#### 3. **Redis for Caching** -// To: -import { v4 as uuid } from 'uuid'; +```yaml +redis: + image: redis:alpine + ports: + - '6379:6379' + volumes: + - redis_data:/data ``` -## πŸš€ Setup Guide +### Running with Docker -### Prerequisites +```bash +# Start all services +docker-compose up -d -1. **Install Supabase CLI**: +# View logs +docker-compose logs -f app - ```bash - npm install -g supabase - ``` +# Stop services +docker-compose down + +# Rebuild and restart +docker-compose up -d --build +``` -2. **Install Docker Desktop** and ensure it's running +--- -### Step 1: Start Supabase Services +## πŸ” Supabase Configuration + +### Local Development Setup + +#### 1. **Supabase CLI Installation** ```bash -# Navigate to your project directory -cd real-time-collaborative-plateform +# Install Supabase CLI +npm install -g supabase -# Start Supabase services -supabase start +# Login to Supabase +supabase login + +# Initialize project +supabase init ``` -### Step 2: Configure Environment Variables +#### 2. **Local Supabase Start** + +```bash +# Start local Supabase +supabase start + +# This will output: +# API URL: http://127.0.0.1:54321 +# DB URL: postgresql://postgres:postgres@127.0.0.1:54321:54322/postgres +# Studio URL: http://127.0.0.1:54323 +# Inbucket URL: http://127.0.0.1:54324 +# JWT secret: super-secret-jwt-token-with-at-least-32-characters-long +# anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +# service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` -Create `.env` file with correct values: +#### 3. **Environment Variables for Local Development** -```env -# Supabase CLI Configuration +```bash +# .env.local NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 -NEXT_PUBLIC_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres -NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... -NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJ... -NEXT_PUBLIC_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_local_anon_key +SUPABASE_SERVICE_ROLE_KEY=your_local_service_role_key NEXT_PUBLIC_SITE_URL=http://localhost:3000 ``` -### Step 3: Install Dependencies +### Production Supabase Setup + +#### 1. **Create Production Project** + +- Go to [supabase.com](https://supabase.com) +- Create new project +- Note down project URL and API keys + +#### 2. **Production Environment Variables** ```bash -yarn install +# .env.production +NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_production_anon_key +SUPABASE_SERVICE_ROLE_KEY=your_production_service_role_key +NEXT_PUBLIC_SITE_URL=https://your-domain.com ``` -### Step 4: Start Development Server +#### 3. **Database Schema Migration** ```bash -yarn dev +# Generate migration from local changes +supabase db diff --schema public + +# Apply migrations to production +supabase db push --db-url "postgresql://postgres:[password]@db.[project-ref].supabase.co:5432/postgres" +``` + +--- + +## πŸ”‘ Authentication System + +### Authentication Flow + +#### 1. **User Registration** + +- **Email/Password**: Traditional signup with email verification +- **OAuth**: Google, GitHub integration +- **Profile Creation**: Automatic user profile creation in `users` table + +#### 2. **Login Process** + +- **Session Management**: JWT-based authentication +- **Middleware Protection**: Route-level authentication checks +- **Redirect Logic**: Authenticated users redirected to dashboard + +#### 3. **Logout Process** + +- **Session Invalidation**: Multiple logout attempts for reliability +- **State Cleanup**: Local storage, cookies, and IndexedDB clearing +- **Redirect**: Dedicated logout page for final cleanup + +### Authentication Components + +#### **Server Actions** (`src/lib/server-action/auth-action.ts`) + +```typescript +// User login +export async function actionLoginUser(formData: FormData) { + // Email/password authentication + // User profile creation + // Redirect to dashboard +} + +// OAuth login +export async function socialLogin(provider: 'google' | 'github') { + // OAuth flow initiation + // Redirect to provider +} + +// User logout +export async function actionLogoutUser() { + // Session cleanup + // Redirect to home +} ``` -## πŸ› Debugging +#### **API Routes** (`src/app/api/auth/callback/route.ts`) + +```typescript +// OAuth callback handling +export async function GET(request: NextRequest) { + // Exchange code for session + // Create user profile + // Redirect to dashboard +} +``` + +#### **Middleware** (`src/middleware.ts`) + +```typescript +// Route protection +export async function middleware(req: NextRequest) { + // Check authentication status + // Redirect unauthenticated users + // Handle public/protected routes +} +``` + +--- + +## 🚨 Common Issues & Solutions + +### 1. **Authentication Issues** + +#### **Problem**: OAuth redirects to `/login?error=auth_failed` + +**Solution**: + +- Check Supabase OAuth configuration +- Verify redirect URLs in Supabase dashboard +- Ensure environment variables are correct + +#### **Problem**: Session persists after logout + +**Solution**: + +- Use dedicated `/logout` page for final cleanup +- Clear all browser storage (localStorage, sessionStorage, cookies) +- Implement aggressive logout with multiple attempts + +#### **Problem**: Middleware redirects to wrong page + +**Solution**: + +- Check route configuration in `middleware.ts` +- Verify public/protected route definitions +- Ensure proper session validation + +### 2. **Database Issues** + +#### **Problem**: User profile not created after signup + +**Solution**: + +- Check `ensureUserProfile` function in `auth-utils.ts` +- Verify database permissions +- Check Supabase RLS policies + +#### **Problem**: Collaborator limit not enforced + +**Solution**: + +- Verify subscription status checking +- Check collaborator count logic +- Ensure proper plan validation + +### 3. **Build & Development Issues** + +#### **Problem**: `Module not found` errors -### Check Authentication Flow +**Solution**: + +- Clear Next.js cache: `rm -rf .next` +- Reinstall dependencies: `pnpm install` +- Check import paths and file structure + +#### **Problem**: Hydration mismatch warnings + +**Solution**: -1. **Server logs**: Look for auth action debug output -2. **Browser console**: Check for user provider logs -3. **Network tab**: Verify cookies are being set -4. **Middleware logs**: Check session detection +- Use `suppressHydrationWarning` for dynamic content +- Ensure consistent server/client rendering +- Check theme switching components -### Check Database Connection +### 4. **Docker Issues** + +#### **Problem**: Port conflicts + +**Solution**: ```bash -# Test database connection -psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -c "SELECT version();" +# Check running containers +docker ps + +# Stop conflicting services +docker-compose down -# List all tables -psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -c "\dt" +# Use different ports +docker-compose up -d -p 3001:3000 ``` -### Check Supabase Services +#### **Problem**: Volume mounting issues + +**Solution**: ```bash -# Check service status -supabase status +# Rebuild containers +docker-compose down +docker-compose up -d --build -# View logs -supabase logs +# Check volume permissions +docker volume ls +docker volume inspect ``` -## πŸ“‹ Common Commands +--- + +## πŸš€ Production Deployment -### Database Operations +### Vercel Deployment + +#### 1. **Environment Variables** + +- Set all production environment variables in Vercel dashboard +- Ensure `NODE_ENV=production` +- Configure Supabase production URLs + +#### 2. **Build Configuration** + +```json +// vercel.json +{ + "buildCommand": "pnpm build", + "outputDirectory": ".next", + "installCommand": "pnpm install", + "framework": "nextjs" +} +``` + +#### 3. **Domain Configuration** + +- Configure custom domain in Vercel +- Update Supabase redirect URLs +- Set up SSL certificates + +### Docker Production + +#### 1. **Production Dockerfile** + +```dockerfile +# Multi-stage build +FROM node:18-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production + +FROM node:18-alpine AS runner +WORKDIR /app +COPY --from=builder /app ./ +COPY . . +EXPOSE 3000 +CMD ["npm", "start"] +``` + +#### 2. **Production Compose** + +```yaml +# docker-compose.prod.yml +version: '3.8' +services: + app: + build: . + ports: + - '3000:3000' + environment: + - NODE_ENV=production + restart: unless-stopped +``` + +--- + +## ⚑ Performance Optimization + +### 1. **Code Splitting** + +- Use dynamic imports for heavy components +- Implement route-based code splitting +- Lazy load non-critical features + +### 2. **Caching Strategy** + +- Implement Redis caching for database queries +- Use Next.js built-in caching +- Optimize image loading with Next.js Image + +### 3. **Database Optimization** + +- Add proper indexes to frequently queried columns +- Implement connection pooling +- Use database views for complex queries + +### 4. **Bundle Optimization** ```bash -# Connect to database -psql postgresql://postgres:postgres@127.0.0.1:54322/postgres +# Analyze bundle size +pnpm build +# Check .next/analyze for bundle analysis + +# Optimize imports +pnpm add @next/bundle-analyzer +``` -# Reset database (if needed) -supabase db reset +--- -# Generate new migration -npx drizzle-kit generate +## πŸ”§ Development Tools -# Push schema changes -npx drizzle-kit push +### 1. **Custom Logger** + +```typescript +// src/utils/logger.ts +import { logger } from '@/utils/logger'; + +// Development-only logging +logger.info('User action', { userId, action }); +logger.error('Error occurred', error); +logger.warn('Warning message', { context }); ``` -### Development +### 2. **Debug Mode** ```bash -# Start development server -yarn dev +# Enable debug bypass in middleware +?debug=bypass -# Build for production -yarn build +# Development environment variables +NODE_ENV=development +DEBUG=true +``` + +### 3. **Database Tools** + +```bash +# Supabase Studio (local) +http://localhost:54323 -# Run linting -yarn lint +# Database connection +psql postgresql://postgres:postgres@localhost:54322/postgres + +# Generate types +supabase gen types typescript --local > types/supabase.ts ``` -## Verification Checklist +--- + +## πŸ“š Additional Resources + +### Documentation + +- [Next.js Documentation](https://nextjs.org/docs) +- [Supabase Documentation](https://supabase.com/docs) +- [Tailwind CSS Documentation](https://tailwindcss.com/docs) +- [shadcn/ui Components](https://ui.shadcn.com/) -- [ ] Supabase CLI is running (`supabase status`) -- [ ] All database tables exist (`\dt` command) -- [ ] Environment variables are correct -- [ ] JWT secret matches Supabase CLI -- [ ] Server-side client is properly configured -- [ ] Auth actions use server-side client -- [ ] User provider has auth state listener -- [ ] Middleware is properly configured -- [ ] No build errors (`yarn dev` runs successfully) -- [ ] Login redirects to dashboard -- [ ] Dashboard loads without errors +### Community -## Still Having Issues? +- [Next.js Discord](https://discord.gg/nextjs) +- [Supabase Discord](https://discord.supabase.com) +- [GitHub Issues](https://github.com/your-repo/issues) -1. **Check the logs**: Look at server console and browser console -2. **Verify environment**: Ensure all environment variables are set correctly -3. **Test step by step**: Try each part of the auth flow individually -4. **Compare with working setup**: Use this troubleshooting guide as reference +### Support -## πŸ“ Recent Fixes Applied +- **Email**: support@yourcompany.com +- **Documentation**: `/help` page in the app +- **GitHub**: Create issues for bugs and feature requests -### Authentication Fixes (Latest) +--- -- Fixed server-side Supabase client configuration -- Updated auth actions to use server-side client -- Added proper session handling in user provider -- Fixed middleware configuration and routing -- Added debugging logs for authentication flow +## πŸŽ‰ Getting Help -### Database Fixes (Latest) +If you encounter issues not covered in this guide: -- Updated database configuration to use correct URL -- Created missing database tables and enum types -- Fixed foreign key relationships -- Resolved JWT secret mismatch +1. **Check the logs** using our custom logger +2. **Search existing issues** on GitHub +3. **Create a new issue** with detailed information +4. **Join our community** for real-time support +5. **Review the Help page** at `/help` in the application -### Build Fixes (Latest) +--- -- Fixed UUID import in dashboard setup -- Resolved merge conflicts in middleware -- Added missing dependencies -- Fixed file structure issues +_Last updated: august 2025_ +_author: Avom brice_ +_check my portfolio: https://maebrieporfolio.vercel.app/_ +_Version: 2.0.0_ diff --git a/public/images/realtime_wp.png b/public/images/realtime_wp.png new file mode 100644 index 0000000..5b091bb Binary files /dev/null and b/public/images/realtime_wp.png differ diff --git a/src/app/(main)/dashboard/layout.tsx b/src/app/(main)/dashboard/layout.tsx index 7a07683..cbda649 100644 --- a/src/app/(main)/dashboard/layout.tsx +++ b/src/app/(main)/dashboard/layout.tsx @@ -23,6 +23,53 @@ const Layout: React.FC = async ({ children, params }) => { products = result.data || []; } + // If no products found, try to sync automatically from Stripe + if (products.length === 0) { + logger.info('No products found in database, attempting automatic sync from Stripe...'); + try { + // Import and run sync function + const { syncStripeProductsAndPrices } = await import('@/utils/sync-stripe-products'); + const syncResult = await syncStripeProductsAndPrices(); + + if (syncResult?.success) { + logger.info(`Auto-sync successful, found ${syncResult.productsCount} products`); + // Fetch products again after sync + const retryResult = await getActiveProductsWithPrice(); + if (retryResult.data && retryResult.data.length > 0) { + products = retryResult.data; + logger.info('Products loaded successfully after auto-sync'); + } else { + logger.warn('Auto-sync succeeded but no products were loaded from database'); + } + } else { + logger.warn('Auto-sync returned success: false'); + } + } catch (syncError) { + logger.error('Auto-sync failed with error:', { + error: syncError, + message: syncError instanceof Error ? syncError.message : 'Unknown error', + stack: syncError instanceof Error ? syncError.stack : 'No stack trace', + }); + + // Check if it's a configuration issue + if (syncError instanceof Error) { + if (syncError.message.includes('STRIPE_SECRET_KEY')) { + logger.error( + 'Stripe configuration issue: STRIPE_SECRET_KEY environment variable is missing' + ); + } else if (syncError.message.includes('Stripe client is not initialized')) { + logger.error('Stripe configuration issue: Stripe client failed to initialize'); + } else if (syncError.message.includes('No active products found')) { + logger.warn( + 'Stripe has no active products. Please create products in your Stripe dashboard first.' + ); + } + } + + // Continue with empty products - user will see fallback UI + } + } + // Debug products loading logger.info('Dashboard Layout - Products loaded:', { productsCount: products.length, diff --git a/src/app/(site)/page.tsx b/src/app/(site)/page.tsx index d2e02c3..6625154 100644 --- a/src/app/(site)/page.tsx +++ b/src/app/(site)/page.tsx @@ -1,5 +1,4 @@ import HomePageComponent from '@/components/features/landing-page'; -import { syncStripeProductsAndPrices } from '@/utils/sync-stripe-products'; import type { Metadata } from 'next'; export const metadata: Metadata = { @@ -23,7 +22,7 @@ export const metadata: Metadata = { }, twitter: { card: 'summary_large_image', - title: 'Home | avom-brice realtime collaborative app', + title: 'avom-brice realtime collaborative app', description: 'Welcome to the real-time collaborative platform. Boost productivity and teamwork with seamless collaboration tools.', images: ['/images/appBanner.png'], @@ -37,7 +36,6 @@ export const metadata: Metadata = { }; const HomePage = () => { - // syncStripeProductsAndPrices(); return ; }; export default HomePage; diff --git a/src/app/help/page.tsx b/src/app/help/page.tsx new file mode 100644 index 0000000..7027261 --- /dev/null +++ b/src/app/help/page.tsx @@ -0,0 +1,560 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { + Users, + FileText, + Settings, + CreditCard, + Zap, + Shield, + Globe, + Smartphone, + HelpCircle, + BookOpen, + MessageCircle, + Mail, + ArrowRight, + CheckCircle, + Star, + Rocket, + Target, + FolderOpen, + PenTool, + Clock, + Search, + Download, + Upload, + Share2, + Eye, + Lock, + Unlock, + Plus, + Minus, +} from 'lucide-react'; + +export default function HelpPage() { + return ( +
+ {/* Hero Section */} +
+
+
+ +
+

+ Complete Dashboard & App Usage Guide +

+

+ Master your collaborative workspace platform with this comprehensive guide. Learn how to + create, collaborate, and manage your projects in real-time. +

+
+ + πŸš€ Real-Time Collaboration + + + πŸ“± Cross-Platform + + + πŸ”’ Secure & Private + +
+
+ + {/* Quick Navigation */} +
+ + +
+ +
+ Getting Started + Account creation and first steps +
+
+ + + +
+ +
+ Collaboration + Team management and real-time editing +
+
+ + + +
+ +
+ Advanced Features + Pro tips and customization +
+
+ + + +
+ +
+ Support + Help and troubleshooting +
+
+
+
+ + {/* Main Content */} +
+ {/* Getting Started Section */} +
+
+

+ πŸš€ Getting Started +

+

+ Follow these steps to get up and running with your collaborative workspace +

+
+ +
+ + + + + Account Creation & Subscription + + + +
+
+
+ + 1 + +
+
+

Visit your app's homepage

+

+ Navigate to the main landing page +

+
+
+ +
+
+ + 2 + +
+
+

Sign Up with email/password or OAuth

+

+ Google or GitHub integration available +

+
+
+ +
+
+ + 3 + +
+
+

Choose your plan

+
+

+ β€’ Free Plan: 2 collaborators max, basic features +

+

+ β€’ Pro Plan: Unlimited collaborators, advanced features +

+
+
+
+ +
+
+ + 4 + +
+
+

Complete secure payment

+

+ Stripe integration for safe transactions +

+
+
+
+
+
+ + + + + + First Login Experience + + + +
+
+
+ + 1 + +
+
+

Dashboard Setup

+

+ Welcome screen for first-time users +

+
+
+ +
+
+ + 2 + +
+
+

Create Workspace

+

+ Set up your first collaborative workspace +

+
+
+ +
+
+ + 3 + +
+
+

Choose Template

+

+ Start with blank or use templates +

+
+
+ +
+
+ + 4 + +
+
+

Set Permissions

+

+ Private or collaborative workspace +

+
+
+
+
+
+
+
+ + {/* Workspace Management Section */} +
+
+

+ 🏒 Workspace Management +

+

+ Organize your projects and team collaboration effectively +

+
+ +
+ + + + + Creating a Workspace + + + +
+
+ +
+

Click "Create Workspace" button

+
+
+ +
+ +
+

Enter workspace details

+

+ Name, icon, and initial permissions +

+
+
+ +
+ +
+

Add collaborators (optional)

+

+ Search by email and invite team members +

+
+
+ +
+ +
+

Set access levels

+

+ Owner, editor, or viewer permissions +

+
+
+
+
+
+ + + + + + Workspace Structure + + + +
+
+ πŸ“ Marketing Team Projects +
+
+ β”œβ”€β”€ 🎨 Brand Guidelines +
+
+ β”œβ”€β”€ πŸ“Š Campaign Strategy +
+
+ β”œβ”€β”€ πŸ“… Content Calendar +
+
+ └── πŸ“ Design Assets +
+
+ β”œβ”€β”€ πŸ“„ Logo Variations +
+
+ └── πŸ“„ Social Media Templates +
+
+
+
+
+
+ + {/* Plan Comparison Section */} +
+
+

+ πŸ’³ Subscription & Billing +

+

+ Choose the plan that fits your team's needs +

+
+ +
+ + + Free Plan + Perfect for small teams getting started + + +
+ $0 + /month +
+
+
+ + 2 collaborators max +
+
+ + 1GB storage +
+
+ + 1 workspace +
+
+ + 7 days version history +
+
+ + Priority support +
+
+ + Advanced features +
+
+
+
+ + +
+ + + Most Popular + +
+ + Pro Plan + For growing teams and businesses + + +
+ $19 + /month +
+
+
+ + Unlimited collaborators +
+
+ + 100GB storage +
+
+ + Unlimited workspaces +
+
+ + 1 year version history +
+
+ + Priority support +
+
+ + Advanced features +
+
+
+
+
+
+ + {/* Support Section */} +
+
+

+ πŸ†˜ Support & Help +

+

Get help when you need it

+
+ +
+ + +
+ +
+ In-App Support +
+ +

+ Chat with our support team directly from the app +

+ +
+
+ + + +
+ +
+ Email Support +
+ +

+ Send detailed questions to our support team +

+ +
+
+ + + +
+ +
+ Documentation +
+ +

+ Comprehensive guides and tutorials +

+ +
+
+
+
+ + {/* CTA Section */} +
+ + +

Ready to Start Collaborating?

+

+ Join thousands of teams already using our platform for real-time collaboration +

+
+ + +
+
+
+
+
+
+ ); +} diff --git a/src/components/features/landing-page/api/data.ts b/src/components/features/landing-page/api/data.ts index fc97d88..bd58d24 100644 --- a/src/components/features/landing-page/api/data.ts +++ b/src/components/features/landing-page/api/data.ts @@ -3,13 +3,8 @@ export const routes = [ { title: 'Resources', href: '/#resources' }, { title: 'Pricing', href: '/#pricing' }, { title: 'Testimonials', href: '/#testimonials' }, - // { title: 'Dashboard', href: '/dashboard' }, - // { title: 'Login', href: '/login' }, - // { title: 'Signup', href: '/signup' }, { title: 'About', href: '/about' }, - // { title: 'Contact', href: '/contact' }, - // { title: 'Privacy Policy', href: '/privacy-policy' }, - // { title: 'Terms of Service', href: '/terms-of-service' }, + { title: 'Help', href: '/help' }, ]; export const components: { title: string; href: string; description: string }[] = [ diff --git a/src/components/features/landing-page/components/footer.tsx b/src/components/features/landing-page/components/footer.tsx index d35d969..d4ab737 100644 --- a/src/components/features/landing-page/components/footer.tsx +++ b/src/components/features/landing-page/components/footer.tsx @@ -55,13 +55,13 @@ export default function Footer() {
-

Contact Us

+

Contact The Author

123 Scalom Ave, Suite 456

YaoundΓ©, Cameroon

- contact@av-digital-workspaces.com + bricefrkc@gmail.com

diff --git a/src/components/features/landing-page/components/header.tsx b/src/components/features/landing-page/components/header.tsx index 0b21595..a1be4ab 100644 --- a/src/components/features/landing-page/components/header.tsx +++ b/src/components/features/landing-page/components/header.tsx @@ -46,143 +46,138 @@ export function Header(props: IAppProps) { return (

- - Cypress Logo - {/* - av-digital-workspaces. - */} - - - - {routes.map((route) => { - const isAnchor = route.href.startsWith('/#'); - const isActive = isAnchor - ? activeHash === route.href.replace('/', '') - : pathname === route.href; - return ( - - - - {route.title} - - - - ); - })} - - -
- -
+
+ {/* Left Section - Logo */} +
+ + Cypress Logo + + Av-digital workspaces + + +
+ + {/* Center Section - Navigation Menu */} +
+ + + {routes.map((route) => { + const isAnchor = route.href.startsWith('/#'); + const isActive = isAnchor + ? activeHash === route.href.replace('/', '') + : pathname === route.href; + return ( + + + + {route.title} + + + + ); + })} + + +
+ + {/* Right Section - Theme Toggle & Authentication */} +
+ - +
+ )} +
+
); } diff --git a/src/components/features/landing-page/landing.page.tsx b/src/components/features/landing-page/landing.page.tsx index f55ae21..7766d19 100644 --- a/src/components/features/landing-page/landing.page.tsx +++ b/src/components/features/landing-page/landing.page.tsx @@ -130,7 +130,7 @@ export default function HomePageComponent() {
{/* 1200px is the max width of the image */} App Banner= 2) { - logger.info('Subscription modal triggered because:', { - subscriptionStatus: subscription?.status, - allCollaborators: collaborators, - collaboratorsCount: collaboratorsWithoutCurrentUser.length, - reason: 'Free plan limit reached or subscription inactive', - }); - // Provide better user feedback about the free plan limit if (collaboratorsWithoutCurrentUser.length >= 2) { toast.error( @@ -477,17 +466,62 @@ export default function SettingsForm() { {permissions === 'shared' && (
- { - addCollaborator(user); - }} - > - - + {/* Guard: Only show CollaboratorSearch if user can add more collaborators */} + {subscription?.status === 'active' || collaboratorsWithoutCurrentUser.length < 2 ? ( + { + addCollaborator(user); + }} + > + + + ) : ( + /* Show upgrade prompt when collaborator limit reached */ +
+
+
+ +
+
+

+ Collaborator Limit Reached +

+

+ You've reached the maximum of 2 collaborators on your Free plan. Upgrade + to Pro for unlimited collaborators and advanced features. +

+
+ + +
+
+
+
+ )} {/* Add information about plan limits */}
diff --git a/src/components/global-components/subscription-modal.tsx b/src/components/global-components/subscription-modal.tsx index e73f7d6..bfd9e8a 100644 --- a/src/components/global-components/subscription-modal.tsx +++ b/src/components/global-components/subscription-modal.tsx @@ -9,7 +9,7 @@ import Loader from './loader'; import { Price, ProductWirhPrice } from '@/lib/supabase/supabase.types'; import { toast } from 'sonner'; import { getStripe } from '@/lib/stripe/stripe-client'; -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../ui/card'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '../ui/card'; import { Badge } from '../ui/badge'; import { PRICING_CARDS } from '@/lib/constant/constants'; import { logger } from '@/utils/logger'; @@ -134,7 +134,7 @@ const SubscriptionModal: React.FC = memo(({ products }) // Check if products array exists and has items if (!products || products.length === 0) { return ( - + Upgrade Your Plan @@ -160,11 +160,11 @@ const SubscriptionModal: React.FC = memo(({ products })
  • β€’ Premium integrations
  • +
    )) : null}
    - {(product.name?.toLowerCase().includes('pro') - ? PRICING_CARDS[1]?.freatures - : PRICING_CARDS[0]?.freatures || [ - 'Unlimited blocks for teams', - 'Unlimited file uploads', - '1 year page history', - 'Invite 10 guests', - ] - ).map((feature, idx) => ( + {/* Use the same features as home page Pro Plan */} + {PRICING_CARDS[1]?.freatures.map((feature, idx) => (
    { }; try { - // insert product to DB using PostgREST API - await postgrestPost('products', productData); + // Try to insert first, if it fails due to duplicate, try to update + try { + await postgrestPost('products', productData); + } catch (error: any) { + // If it's a duplicate key error, try to update instead + if (error.message?.includes('duplicate') || error.message?.includes('already exists')) { + await postgrestPut('products', productData, { id: `eq.${product.id}` }); + } else { + throw error; + } + } } catch (error: Error | any) { - throw new Error(error.message); + console.error(`Error upserting product ${product.id}:`, error); + throw new Error(`Failed to upsert product: ${error.message}`); } }; @@ -50,9 +60,20 @@ export const upsertPriceRecord = async (price: Stripe.Price) => { }; try { - await postgrestPost('prices', priceData); + // Try to insert first, if it fails due to duplicate, try to update + try { + await postgrestPost('prices', priceData); + } catch (error: any) { + // If it's a duplicate key error, try to update instead + if (error.message?.includes('duplicate') || error.message?.includes('already exists')) { + await postgrestPut('prices', priceData, { id: `eq.${price.id}` }); + } else { + throw error; + } + } } catch (error: Error | any) { - throw new Error(error.message); + console.error(`Error upserting price ${price.id}:`, error); + throw new Error(`Failed to upsert price: ${error.message}`); } }; diff --git a/src/lib/supabase/queries.ts b/src/lib/supabase/queries.ts index a8b9caf..9564ee9 100644 --- a/src/lib/supabase/queries.ts +++ b/src/lib/supabase/queries.ts @@ -1,6 +1,7 @@ 'use server'; import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete } from '@/utils/client'; import { Subscription, User, workspace, File, Folder } from './supabase.types'; +import logger from '@/utils/logger'; /** * Retrieves the subscription status of a user. @@ -23,7 +24,7 @@ export const getUserSubscriptionStatus = async (userId: string) => { }; } } catch (error) { - console.error('getUserSubscriptionStatus error:', error); + logger.error('getUserSubscriptionStatus error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, @@ -43,7 +44,7 @@ export const createWorkspace = async (workspace: workspace) => { const result = await postgrestPost('workspaces', workspace); return { data: result, error: null }; } catch (error) { - console.error('createWorkspace error:', error); + logger.error('createWorkspace error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -65,7 +66,7 @@ export const getFiles = async (folderId: string) => { }); return { data: results as File[], error: null }; } catch (error) { - console.error('getFiles error:', error); + logger.error('getFiles error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -92,7 +93,7 @@ export const getFolders = async (workspaceId: string) => { }); return { data: results as Folder[], error: null }; } catch (error) { - console.error('getFolders error:', error); + logger.error('getFolders error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -119,7 +120,7 @@ export const getPrivateWorkspaces = async (userId: string) => { // In a real implementation, we'd need a more complex query return { data: results as workspace[], error: null }; } catch (error) { - console.error('getPrivateWorkspaces error:', error); + logger.error('getPrivateWorkspaces error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -157,7 +158,7 @@ export const getCollaboratingWorkspaces = async (userId: string) => { return { data: workspaceResults as workspace[], error: null }; } catch (error) { - console.error('getCollaboratingWorkspaces error:', error); + logger.error('getCollaboratingWorkspaces error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -183,7 +184,7 @@ export const getSharedWorkspaces = async (userId: string) => { // This is a simplified approach - in reality you'd need to check collaborators table return { data: results as workspace[], error: null }; } catch (error) { - console.error('getSharedWorkspaces error:', error); + logger.error('getSharedWorkspaces error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -205,7 +206,7 @@ export const getUsersFromSearch = async (query: string) => { }); return { data: results as User[], error: null }; } catch (error) { - console.error('getUsersFromSearch error:', error); + logger.error('getUsersFromSearch error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -221,21 +222,21 @@ export const getWorkspaceDetails = async (workspaceId: string) => { if (!workspaceId) return { data: [], error: 'Workspace ID is required' }; try { - // console.log('πŸ” getWorkspaceDetails: Fetching workspace with ID:', workspaceId); + // logger.log('πŸ” getWorkspaceDetails: Fetching workspace with ID:', workspaceId); // Use client-side Supabase client for client components const results = await postgrestGet('workspaces', { id: `eq.${workspaceId}` }); - // console.log('πŸ” getWorkspaceDetails: PostgREST results:', results); + // logger.log('πŸ” getWorkspaceDetails: PostgREST results:', results); if (results && results.length > 0) { - // console.log('βœ… getWorkspaceDetails: Found workspace:', results[0]); + // logger.log('βœ… getWorkspaceDetails: Found workspace:', results[0]); return { data: results as workspace[], error: null }; } - console.log('❌ getWorkspaceDetails: No workspace found with ID:', workspaceId); + logger.error('❌ getWorkspaceDetails: No workspace found with ID:', workspaceId); return { data: [], error: 'Workspace not found' }; } catch (error) { - console.error('❌ getWorkspaceDetails error:', error); + logger.error('❌ getWorkspaceDetails error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: [], error: errorMessage }; } @@ -251,19 +252,19 @@ export const getFileDetails = async (fileId: string) => { if (!fileId) return { data: [], error: 'File ID is required' }; try { - console.log('πŸ” getFileDetails: Fetching file with ID:', fileId); + logger.info('πŸ” getFileDetails: Fetching file with ID:', fileId); const results = await postgrestGet('files', { id: `eq.${fileId}` }); - console.log('πŸ” getFileDetails: PostgREST results:', results); + logger.info('πŸ” getFileDetails: PostgREST results:', results); if (results && results.length > 0) { - console.log('βœ… getFileDetails: Found file:', results[0]); + logger.info('βœ… getFileDetails: Found file:', results[0]); return { data: results as File[], error: null }; } - console.log('❌ getFileDetails: No file found with ID:', fileId); + logger.info('❌ getFileDetails: No file found with ID:', fileId); return { data: [], error: 'File not found' }; } catch (error) { - console.error('❌ getFileDetails error:', error); + logger.error('❌ getFileDetails error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: [], error: errorMessage }; } @@ -279,19 +280,19 @@ export const getFolderDetails = async (folderId: string) => { if (!folderId) return { data: [], error: 'Folder ID is required' }; try { - console.log('πŸ” getFolderDetails: Fetching folder with ID:', folderId); + logger.info('πŸ” getFolderDetails: Fetching folder with ID:', folderId); const results = await postgrestGet('folders', { id: `eq.${folderId}` }); - console.log('πŸ” getFolderDetails: PostgREST results:', results); + logger.info('πŸ” getFolderDetails: PostgREST results:', results); if (results && results.length > 0) { - console.log('βœ… getFolderDetails: Found folder:', results[0]); + // logger.info('βœ… getFolderDetails: Found folder:', results[0]); return { data: results as Folder[], error: null }; } - console.log('❌ getFolderDetails: No folder found with ID:', folderId); + logger.info('❌ getFolderDetails: No folder found with ID:', folderId); return { data: [], error: 'Folder not found' }; } catch (error) { - console.error('❌ getFolderDetails error:', error); + logger.error('❌ getFolderDetails error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: [], error: errorMessage }; } @@ -308,7 +309,7 @@ export const deleteFile = async (fileId: string) => { try { await postgrestDelete('files', { id: `eq.${fileId}` }); } catch (error) { - console.error('deleteFile error:', error); + logger.error('deleteFile error:', error); throw error; } }; @@ -323,7 +324,7 @@ export const deleteFolder = async (folderId: string) => { try { await postgrestDelete('folders', { id: `eq.${folderId}` }); } catch (error) { - console.error('deleteFolder error:', error); + logger.error('deleteFolder error:', error); throw error; } }; @@ -339,7 +340,7 @@ export const deleteWorkspace = async (workspaceId: string) => { try { await postgrestDelete('workspaces', { id: `eq.${workspaceId}` }); } catch (error) { - console.error('deleteWorkspace error:', error); + logger.error('deleteWorkspace error:', error); throw error; } }; @@ -401,7 +402,7 @@ export const getCollaborators = async (workspaceId: string) => { return { data: allUsers, error: null }; } catch (error) { - console.error('getCollaborators error:', error); + logger.error('getCollaborators error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { data: null, error: errorMessage }; } @@ -423,7 +424,7 @@ export const findUser = async (userId: string) => { } return null; } catch (error) { - console.error('findUser error:', error); + logger.error('findUser error:', error); return null; } }; @@ -439,7 +440,7 @@ export const createFolder = async (folder: Folder) => { const result = await postgrestPost('folders', folder); return { data: result, error: null }; } catch (error) { - console.error('createFolder error:', error); + logger.error('createFolder error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -456,7 +457,7 @@ export const createFile = async (file: File) => { const result = await postgrestPost('files', file); return { data: result, error: null }; } catch (error) { - console.error('createFile error:', error); + logger.error('createFile error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -474,7 +475,7 @@ export const updateFolder = async (updates: Partial, folderId: string) = const result = await postgrestPut('folders', updates, { id: `eq.${folderId}` }); return { data: result, error: null }; } catch (error) { - console.error('updateFolder error:', error); + logger.error('updateFolder error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -492,7 +493,7 @@ export const updateFile = async (updates: Partial, fileId: string) => { const result = await postgrestPut('files', updates, { id: `eq.${fileId}` }); return { data: result, error: null }; } catch (error) { - console.error('updateFile error:', error); + logger.error('updateFile error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -510,7 +511,7 @@ export const updateWorkspace = async (updates: Partial, workspaceId: const result = await postgrestPut('workspaces', updates, { id: `eq.${workspaceId}` }); return { data: result, error: null }; } catch (error) { - console.error('updateWorkspace error:', error); + logger.error('updateWorkspace error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -528,7 +529,7 @@ export const updateUser = async (updates: Partial, userId: string) => { const result = await postgrestPut('users', updates, { id: `eq.${userId}` }); return { data: result, error: null }; } catch (error) { - console.error('updateUser error:', error); + logger.error('updateUser error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown database error'; return { data: null, error: errorMessage }; } @@ -549,7 +550,7 @@ export const addCollaborators = async (users: User[], workspaceId: string) => { await postgrestPost('collaborators', collaboratorData); } catch (error) { - console.error('addCollaborators error:', error); + logger.error('addCollaborators error:', error); throw error; } }; @@ -571,7 +572,7 @@ export const removeCollaborators = async (users: User[], workspaceId: string) => await Promise.all(promises); } catch (error) { - console.error('removeCollaborators error:', error); + logger.error('removeCollaborators error:', error); throw error; } }; @@ -607,7 +608,7 @@ export const getActiveProductsWithPrice = async () => { prices: prices || [], }; } catch (error) { - console.error(`Error fetching prices for product ${product.id}:`, error); + logger.error(`Error fetching prices for product ${product.id}:`, error); return { ...product, prices: [], @@ -618,7 +619,7 @@ export const getActiveProductsWithPrice = async () => { return { data: productsWithPrices, error: null }; } catch (error) { - console.error('Database error in getActiveProductsWithPrice:', error); + logger.error('Database error in getActiveProductsWithPrice:', error); const errorMessage = error instanceof Error ? error.message : 'Database connection failed'; return { data: [], error: errorMessage }; } diff --git a/src/middleware.ts b/src/middleware.ts index cdc7360..8be477f 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -6,7 +6,8 @@ export async function middleware(req: NextRequest) { // Debug bypass option if (req.nextUrl.searchParams.get('debug') === 'bypass') { - console.log('Middleware: Debug bypass enabled, skipping all checks'); + // Note: We can't use logger here as it's not available in middleware + // This is a development-only feature return NextResponse.next(); } @@ -28,19 +29,13 @@ export async function middleware(req: NextRequest) { const isProtectedRoute = protectedRoutes.some((route) => pathname.startsWith(route)); const isPublicRoute = publicRoutes.includes(pathname); - console.log('Middleware: Processing route:', pathname, { - isProtectedRoute, - isPublicRoute, - userAgent: req.headers.get('user-agent')?.substring(0, 50), - }); - try { // Create middleware-safe Supabase client const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://127.0.0.1:54321'; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; if (!supabaseUrl || !supabaseAnonKey) { - console.error('Middleware: Missing Supabase configuration'); + // Note: We can't use logger here as it's not available in middleware return NextResponse.next(); } @@ -60,31 +55,18 @@ export async function middleware(req: NextRequest) { // For protected routes, verify authentication if (isProtectedRoute) { - console.log('Middleware: Checking authentication for protected route:', pathname); const { data: { session }, error, } = await supabase.auth.getSession(); - console.log('Middleware: Session check result:', { - hasSession: !!session, - hasUser: !!session?.user, - userId: session?.user?.id, - userEmail: session?.user?.email, - hasAccessToken: !!session?.access_token, - expiresAt: session?.expires_at, - error: error?.message, - }); - if (error || !session?.user) { - console.log('Middleware: No valid session found, redirecting to home page'); // Redirect to home page instead of login page return NextResponse.redirect(new URL('/', req.url)); } // Additional validation: check if user ID exists and session is not expired if (!session.user.id || !session.access_token) { - console.log('Middleware: Invalid session data, redirecting to home page'); // Redirect to home page instead of login page return NextResponse.redirect(new URL('/', req.url)); } @@ -93,25 +75,17 @@ export async function middleware(req: NextRequest) { if (session.expires_at) { const now = Math.floor(Date.now() / 1000); if (now >= session.expires_at) { - console.log('Middleware: Session expired, redirecting to home page'); // Redirect to home page instead of login page return NextResponse.redirect(new URL('/', req.url)); } } - - console.log('Middleware: Valid session found for user:', session.user.email); } // For public auth routes, redirect authenticated users to dashboard if (isPublicRoute && pathname !== '/') { - console.log('Middleware: Checking authentication for public auth route:', pathname); - // Check if logout has recently occurred via query parameter const fromLogout = req.nextUrl.searchParams.get('fromLogout'); if (fromLogout === 'true') { - console.log( - 'Middleware: Logout detected via query param, bypassing session check for public auth route' - ); return NextResponse.next(); } @@ -119,23 +93,11 @@ export async function middleware(req: NextRequest) { data: { session }, } = await supabase.auth.getSession(); - console.log('Middleware: Public route session check:', { - hasSession: !!session, - hasUser: !!session?.user, - userId: session?.user?.id, - userEmail: session?.user?.email, - hasAccessToken: !!session?.access_token, - expiresAt: session?.expires_at, - pathname, - fromLogout, - }); - if (session?.user && session.user.id && session.access_token) { // Check if session is expired if (session.expires_at) { const now = Math.floor(Date.now() / 1000); if (now >= session.expires_at) { - console.log('Middleware: Session expired for public auth route'); return NextResponse.next(); } } @@ -148,28 +110,19 @@ export async function middleware(req: NextRequest) { error: userError, } = await supabase.auth.getUser(); if (userError || !user) { - console.log('Middleware: Session has invalid user, treating as logged out'); return NextResponse.next(); } // If we get here, the session is actually valid - console.log('Middleware: User authenticated, redirecting to dashboard'); return NextResponse.redirect(new URL('/dashboard', req.url)); } catch (userCheckError) { - console.log( - 'Middleware: Error checking user validity, treating as logged out:', - userCheckError - ); return NextResponse.next(); } - } else { - console.log('Middleware: No valid session found for public auth route'); } } return NextResponse.next(); } catch (error) { - console.error('Middleware error:', error); // On error, allow the request to proceed return NextResponse.next(); } diff --git a/src/utils/sync-stripe-products.ts b/src/utils/sync-stripe-products.ts index 7121c82..a194ac5 100644 --- a/src/utils/sync-stripe-products.ts +++ b/src/utils/sync-stripe-products.ts @@ -1,20 +1,81 @@ import { stripe } from '../lib/stripe'; import { upsertProductRecord, upsertPriceRecord } from '../lib/stripe/admin-tasks'; +import { logger } from './logger'; export async function syncStripeProductsAndPrices() { - console.log('\n\n syncing stripe products and prices'); + logger.info('Starting Stripe products and prices sync...'); + + // Check if Stripe is properly configured + if (!stripe) { + const error = 'Stripe client is not initialized'; + logger.error('❌ Stripe configuration error:', error); + throw new Error(error); + } + + // Check if Stripe secret key is available + if (!process.env.STRIPE_SECRET_KEY) { + const error = 'STRIPE_SECRET_KEY environment variable is not set'; + logger.error('❌ Stripe configuration error:', error); + throw new Error(error); + } + try { + logger.info('Fetching products from Stripe...'); const products = await stripe.products.list({ active: true, limit: 100 }); + logger.info(`Found ${products.data.length} active products in Stripe`); + + if (products.data.length === 0) { + logger.warn( + '⚠️ No active products found in Stripe. Please create products in your Stripe dashboard first.' + ); + return { success: true, productsCount: 0, message: 'No products to sync' }; + } + for (const product of products.data) { - await upsertProductRecord(product); - const prices = await stripe.prices.list({ product: product.id, active: true, limit: 100 }); - for (const price of prices.data) { - await upsertPriceRecord(price); + logger.info(`Syncing product: ${product.name} (${product.id})`); + try { + await upsertProductRecord(product); + logger.info(`βœ… Product synced: ${product.name}`); + } catch (productError) { + logger.error(`❌ Failed to sync product ${product.name}:`, productError); + // Continue with other products instead of failing completely + continue; + } + + try { + const prices = await stripe.prices.list({ product: product.id, active: true, limit: 100 }); + logger.info(`Found ${prices.data.length} active prices for product ${product.name}`); + + for (const price of prices.data) { + logger.info(`Syncing price: ${price.id} for product ${product.name}`); + try { + await upsertPriceRecord(price); + logger.info(`βœ… Price synced: ${price.id}`); + } catch (priceError) { + logger.error(`❌ Failed to sync price ${price.id}:`, priceError); + // Continue with other prices + continue; + } + } + } catch (pricesError) { + logger.error(`❌ Failed to fetch prices for product ${product.name}:`, pricesError); + // Continue with other products + continue; } } - console.log('βœ… Stripe products and prices synced successfully!'); + + logger.info('βœ… Stripe products and prices sync completed!'); + return { success: true, productsCount: products.data.length }; } catch (err) { - console.error('❌ Error syncing Stripe products/prices:', err); - process.exit(1); + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + const errorStack = err instanceof Error ? err.stack : 'No stack trace available'; + + logger.error('❌ Error syncing Stripe products/prices:', { + message: errorMessage, + stack: errorStack, + error: err, + }); + + throw new Error(`Stripe sync failed: ${errorMessage}`); } }