From 6bf998a52a9bf6986a237ddf39611bc367ab883e Mon Sep 17 00:00:00 2001 From: Adem Date: Wed, 25 Mar 2026 13:52:45 +0100 Subject: [PATCH] PreRelease v1 --- .vscode/settings.json | 3 + DASHBOARD_REDESIGN.md | 274 ++ NOTIFICATION_IMPLEMENTATION_SUMMARY.md | 506 ++++ NOTIFICATION_SYSTEM_SETUP.md | 386 +++ PROJECT_ARCHITECTURE_AND_STACK.md | 208 ++ app/(auth)/sign-in/[[...sign-in]]/page.tsx | 33 + app/(auth)/sign-up/[[...sign-up]]/page.tsx | 27 + app/(dashboard)/contacts/layout.tsx | 16 + app/(dashboard)/contacts/page.tsx | 110 + app/(dashboard)/dashboard/page.tsx | 814 ++++++ app/(dashboard)/layout.tsx | 22 + app/api/uploadthing/core.ts | 8 + app/api/uploadthing/route.ts | 3 + app/api/webhooks/clerk/route.ts | 164 ++ app/clerk-provider.tsx | 30 + app/globals.css | 32 + app/layout.tsx | 94 +- app/provider.tsx | 5 +- architecture.png | Bin 0 -> 5574 bytes components/ui/resizable.tsx | 32 +- components/views/Home/Hero.tsx | 103 +- components/views/Home/HowItWorks.tsx | 2 +- components/views/Home/Navbar.tsx | 191 +- components/views/dashboard/charts.tsx | 295 ++ .../views/dashboard/contacts-header.tsx | 44 + .../views/dashboard/contract-upload-form.tsx | 141 + components/views/dashboard/contracts-list.tsx | 1078 +++++++ .../views/dashboard/empty-contracts-state.tsx | 47 + components/views/dashboard/navigation.tsx | 162 ++ .../views/dashboard/notification-bar.tsx | 617 ++++ hooks/useNotifications.ts | 269 ++ lib/actions/contract.action.ts | 530 ++++ lib/actions/notification.action.ts | 422 +++ lib/actions/stats.action.ts | 14 + lib/actions/user.action.ts | 49 + lib/db/prisma.ts | 19 + lib/services/ai.service.ts | 890 ++++++ lib/services/contract.service.ts | 556 ++++ lib/services/notification.service.ts | 634 +++++ lib/services/stats.service.ts | 367 +++ lib/services/storage.service.ts | 69 + lib/upload.ts | 50 + package-lock.json | 2492 ++++++++++++----- package.json | 14 +- prisma/contract.prisma | 0 prisma/schema.prisma | 130 + prisma/user.prisma | 0 proxy.ts | 44 + public/Contrat_Credit_Immobilier_BTE.pdf | Bin 0 -> 222850 bytes public/manifest.json | 23 + scripts/package.json | 6 + scripts/sync-existing-user.ts | 63 + scripts/sync-users.ts | 49 + setup-notifications.sh | 50 + tailwind.config.ts | 4 + types/contract.types.ts | 83 + 56 files changed, 11427 insertions(+), 847 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 DASHBOARD_REDESIGN.md create mode 100644 NOTIFICATION_IMPLEMENTATION_SUMMARY.md create mode 100644 NOTIFICATION_SYSTEM_SETUP.md create mode 100644 PROJECT_ARCHITECTURE_AND_STACK.md create mode 100644 app/(auth)/sign-in/[[...sign-in]]/page.tsx create mode 100644 app/(auth)/sign-up/[[...sign-up]]/page.tsx create mode 100644 app/(dashboard)/contacts/layout.tsx create mode 100644 app/(dashboard)/contacts/page.tsx create mode 100644 app/(dashboard)/dashboard/page.tsx create mode 100644 app/(dashboard)/layout.tsx create mode 100644 app/api/uploadthing/core.ts create mode 100644 app/api/uploadthing/route.ts create mode 100644 app/api/webhooks/clerk/route.ts create mode 100644 app/clerk-provider.tsx create mode 100644 architecture.png create mode 100644 components/views/dashboard/charts.tsx create mode 100644 components/views/dashboard/contacts-header.tsx create mode 100644 components/views/dashboard/contract-upload-form.tsx create mode 100644 components/views/dashboard/contracts-list.tsx create mode 100644 components/views/dashboard/empty-contracts-state.tsx create mode 100644 components/views/dashboard/navigation.tsx create mode 100644 components/views/dashboard/notification-bar.tsx create mode 100644 hooks/useNotifications.ts create mode 100644 lib/actions/contract.action.ts create mode 100644 lib/actions/notification.action.ts create mode 100644 lib/actions/stats.action.ts create mode 100644 lib/actions/user.action.ts create mode 100644 lib/db/prisma.ts create mode 100644 lib/services/ai.service.ts create mode 100644 lib/services/contract.service.ts create mode 100644 lib/services/notification.service.ts create mode 100644 lib/services/stats.service.ts create mode 100644 lib/services/storage.service.ts create mode 100644 lib/upload.ts create mode 100644 prisma/contract.prisma create mode 100644 prisma/schema.prisma create mode 100644 prisma/user.prisma create mode 100644 proxy.ts create mode 100644 public/Contrat_Credit_Immobilier_BTE.pdf create mode 100644 public/manifest.json create mode 100644 scripts/package.json create mode 100644 scripts/sync-existing-user.ts create mode 100644 scripts/sync-users.ts create mode 100644 setup-notifications.sh create mode 100644 types/contract.types.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..97f0e81 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "prisma.pinToPrisma6": true +} diff --git a/DASHBOARD_REDESIGN.md b/DASHBOARD_REDESIGN.md new file mode 100644 index 0000000..179f30d --- /dev/null +++ b/DASHBOARD_REDESIGN.md @@ -0,0 +1,274 @@ +# Complete Dashboard Redesign - Implementation Summary + +## 🎯 Architecture Overview + +The dashboard has been reorganized into two distinct routes with a persistent navigation sidebar: + +### Route Structure + +``` +/dashboard β†’ Analytics Hub (Stats & Charts) +/contacts β†’ Contracts Manager (Upload & Management) +``` + +--- + +## πŸ“Š New /dashboard Route Features + +### 1. **Stats Components** + +- **6 Key Metric Cards** displaying: + - Total Contracts (with trend indicator) + - Analyzed Contracts (with analysis rate %) + - In Progress (with processing indicator) + - Failed Contracts (with alert) + - Average Premium ($) + - Analysis Success Rate (%) + +- **Visual Design:** + - Gradient backgrounds (blue, green, amber, red, purple, cyan) + - Icon indicators for each metric + - Trend indicators with directional arrows + - Smooth animations on load + +### 2. **Advanced Charts (using Recharts)** + +- **Upload Trends Chart** (Area Chart) + - 30-day trend visualization + - Interactive tooltips + - Gradient fill effects +- **Contract Type Distribution** (Bar Chart) + - Visual breakdown by contract type + - Color-coded bars + - Responsive design +- **Contract Status Overview** (Pie Chart) + - Visual representation of processing statuses + - Color-coded segments + - Interactive labels +- **Distribution Radar Chart** (Coming in enhanced version) + - Multi-dimensional data visualization + +### 3. **Key Insights Section** + +- **Analysis Efficiency Card** + - Success rate progress bar with animation + - Real-time calculation from database +- **Processing Queue Card** + - In-progress contracts count + - Failed contracts indicator +- **Premium Metrics Card** + - Total premium analysis + - Number of analyzed contracts + +### 4. **Quick Actions** + +- **Upload New Contract** β†’ Links to /contacts +- **View All Contracts** β†’ Links to /contacts with count + +--- + +## πŸ—‚οΈ New /contacts Route Features + +### 1. **Professional Header** + +- Breadcrumb navigation back to dashboard +- Gradient title with descriptive subtitle +- Quick stats badges highlighting key features + +### 2. **Contract Upload Section** + +- Drag-and-drop file upload +- AI-powered analysis indication +- Real-time upload feedback + +### 3. **Contracts List & Management** + +- Existing contracts display with new styling +- Analysis status indicators +- Ask questions feature (existing, now integrated) +- Contract details modal + +--- + +## 🎨 Design System Enhancements + +### Visual Elements + +- **Animated Gradient Backgrounds** + - Floating blob animations in background + - Smooth color transitions + - Dark mode optimized + +- **Color Palette** + - Primary: Blue (#3B82F6) + - Accent: Gradient colors + - Semantic colors for status indicators + +- **Typography** + - Bold headlines with gradient text + - Clear visual hierarchy + - Responsive font sizing + +- **Animations** + - Fade-in effects on page load + - Smooth transitions between states + - Motion/Framer Motion integration + - Staggered card animations + +### Components + +- Shadcn/UI for consistent design +- Custom stat cards with gradients +- Backdrop blur effects +- Border styling with transparency + +--- + +## πŸ”§ Technical Implementation + +### New Files Created + +1. **Services** + - `lib/services/stats.service.ts` β†’ Database queries for analytics + - `lib/actions/stats.action.ts` β†’ Server action wrapper + +2. **Components** + - `components/views/dashboard/stat-cards.tsx` β†’ Metric cards + - `components/views/dashboard/charts.tsx` β†’ Recharts integration + - `components/views/dashboard/navigation.tsx` β†’ Sidebar navigation + - `components/views/dashboard/contacts-header.tsx` β†’ Contacts page header + +3. **Routes** + - `app/(dashboard)/contacts/layout.tsx` β†’ Contacts route layout + - `app/(dashboard)/contacts/page.tsx` β†’ Contacts page with upload & list + +4. **Updated Files** + - `app/(dashboard)/layout.tsx` β†’ Sidebar integration + - `app/(dashboard)/dashboard/page.tsx` β†’ New analytics dashboard + +--- + +## πŸ“ˆ Data & Analytics + +### Stats Service Features + +```typescript +{ + stats: { + totalContracts: number + analyzedContracts: number + processingContracts: number + failedContracts: number + analysisRate: percentage + }, + chartData: { + byType: Array<{type, count}> + byStatus: Array<{status, count}> + trends: Array<{date, count}> // Last 30 days + }, + premiumInfo: { + averagePremium: number + totalPremium: number + count: number + }, + recentContracts: Array<{id, title, type, createdAt, premium}> +} +``` + +### Database Queries + +- Aggregated counts by status, type, and date +- Premium statistics (avg, sum, count) +- 30-day trend analysis +- Recent contracts ranking + +--- + +## 🎯 Navigation System + +### Sidebar Features + +- **Logo with brand identity** +- **Two main navigation items** + - Analytics (Dashboard) + - Contracts (Management) +- **Theme toggle** (Light/Dark mode) +- **Sign out button** +- **Responsive active states** +- **Smooth transitions and animations** + +--- + +## πŸš€ Performance & UX + +### Optimizations + +- Client-side state management for smooth interactions +- Server-side analytics computation +- Lazy loading of components +- Efficient database queries with aggregation +- Responsive design for all screen sizes + +### Accessibility + +- Semantic HTML structure +- ARIA labels on icons +- Clear visual hierarchy +- High contrast ratios +- Keyboard navigation support + +--- + +## πŸ“± Responsive Design + +- **Desktop (1024px+)**: Full sidebar + expanded content +- **Tablet (768px-1023px)**: Flexible grid layouts +- **Mobile**: Responsive charts and card layouts + +--- + +## 🎬 Next Steps & Enhancements + +### Future Improvements + +1. **Export Reports** β†’ PDF/Excel export functionality +2. **Advanced Filtering** β†’ Filter contracts by date, type, status +3. **Custom Dashboards** β†’ User-customizable dashboard layouts +4. **Real-time WebSocket Updates** β†’ Live processing status +5. **Performance Optimization** β†’ Data caching and infinite scroll +6. **Dark Mode Enhancements** β†’ More sophisticated theme system +7. **Mobile App** β†’ Native mobile experience + +--- + +## πŸ” Security & Authorization + +- User ID verification in all server actions +- Clerk authentication integration +- Database queries filtered by userId +- Server-side data serialization + +--- + +## πŸ“¦ Dependencies Used + +- `recharts` (v3.7.0) - Chart visualizations +- `motion` - Animations +- `lucide-react` - Icons +- `shadcn/ui` - UI components +- `@clerk/nextjs` - Authentication + +--- + +## βœ… Testing Checklist + +- [x] All TypeScript types compile correctly +- [x] No console errors on load +- [x] Navigation between routes works smoothly +- [x] Stats query returns valid data +- [x] Charts render with sample data +- [x] Responsive design on mobile/tablet +- [x] Dark mode toggle functions +- [x] Animations perform smoothly +- [x] Sidebar navigation is responsive +- [x] User authentication required diff --git a/NOTIFICATION_IMPLEMENTATION_SUMMARY.md b/NOTIFICATION_IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..f2a1628 --- /dev/null +++ b/NOTIFICATION_IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,506 @@ +# πŸ”” Notification System Implementation - Complete Guide + +## ✨ What Was Implemented + +You've successfully enabled Option 2: **Renewal and Deadline Assistant** with comprehensive notification system. + +### 🎯 Key Features + +1. **Toast Notifications** (Sonner) + - βœ… Contract uploaded successfully + - βœ… Contract analyzed successfully (or error with reason) + - βœ… Contract deleted successfully + - ❌ Error messages with detailed feedback + - πŸ”” Deadline alerts for upcoming expirations + +2. **Persistent Notifications Database** + - All notifications are stored permanently + - 5 notification types: SUCCESS, ERROR, WARNING, INFO, DEADLINE + - Notifications linked to specific contracts + - Auto-expiration after 30 days (configurable) + +3. **Notification Bar UI** + - Beautiful bell icon with unread count badge + - Dropdown showing recent 15 notifications + - Type-specific icons and colors + - Action buttons to mark as read or delete + - Time formatting (e.g., "2m ago", "1h ago") + - Empty state when no notifications + - Auto-refresh every 30 seconds when open + +4. **Deadline Detection & Alerts** + - πŸ”΄ **30 Days Before Expiration**: CRITICAL notification + - 🟠 **15 Days Before Expiration**: WARNING notification + - 🟑 **7 Days Before Expiration**: URGENT notification + - Daily automatic check on dashboard load + - Smart deduplication (max 1 notification per threshold per day) + +5. **Well-Documented Code** + - 1000+ lines of comprehensive inline comments + - JSDoc comments for all functions + - Step-by-step explanations of processing pipelines + - Examples and usage patterns + +## πŸ“ Files Created/Modified + +### New Files Created + +``` +✨ lib/services/notification.service.ts (580 lines) - Core notification logic +✨ lib/actions/notification.action.ts (340 lines) - Server actions for notifications +✨ components/views/dashboard/notification-bar.tsx (490 lines) - Notification UI component +✨ hooks/useNotifications.ts (220 lines) - React hook for toast + notifications +✨ NOTIFICATION_SYSTEM_SETUP.md (400 lines) - Detailed setup guide +✨ setup-notifications.sh (30 lines) - Automated setup script +``` + +### Files Modified + +``` +πŸ“ prisma/schema.prisma - Added Notification model, NotificationType enum +πŸ“ lib/actions/contract.action.ts - Added notifications on upload/analyze/delete +πŸ“ lib/services/contract.service.ts - Added getUserByClerkId() method +πŸ“ components/views/dashboard/navigation.tsx - Added NotificationBar component +πŸ“ app/(dashboard)/dashboard/page.tsx - Added checkDeadlineNotifications() call +``` + +## πŸš€ Quick Start + +### 1. Run Database Migration + +```bash +npx prisma migrate dev --name add_notifications +``` + +This creates: + +- `Notification` table with indexes +- `NotificationType` enum (SUCCESS, WARNING, ERROR, INFO, DEADLINE) +- Relations between User, Contract, and Notification + +### 2. Generate Prisma Client + +```bash +npx prisma generate +``` + +Or use the automated setup script: + +```bash +bash setup-notifications.sh +``` + +### 3. Start Development Server + +```bash +npm run dev +``` + +## πŸ“Š Architecture Overview + +### Notification Flow + +``` +User Action (upload/analyze/delete) + ↓ +Contract Server Action + ↓ +β”œβ”€ Execute operation (save/analyze/delete) +β”œβ”€ Create Sonner toast (immediate UI feedback) +└─ Create database notification (persistent) + ↓ +Notification Service + ↓ +β”œβ”€ Store in database +β”œβ”€ Assign expiration time +└─ Link to contract + ↓ +Notification Bar + ↓ +β”œβ”€ Display bell icon with unread count +β”œβ”€ Show in dropdown when clicked +└─ Allow user interaction (mark read, delete) +``` + +### Deadline Notification Flow + +``` +Dashboard Page Load + ↓ +checkDeadlineNotifications() called + ↓ +Notification Service + ↓ +β”œβ”€ Query all user contracts with endDate +β”œβ”€ Calculate days until expiration +β”œβ”€ Check if 30, 15, or 7 days away +β”œβ”€ Avoid duplicate notifications +└─ Create deadline notifications + ↓ +Stored in database + ↓ +Display in Notification Bar +``` + +## πŸ’» Usage Examples + +### Example 1: Upload Contract + +**User Action**: Click upload button, select file +**Flow**: + +1. File uploaded to UploadThing +2. `saveContract()` server action triggered +3. Toast appears: "πŸ“„ Contract Uploaded" +4. Notification created and stored in database +5. User can see notification in bell icon dropdown + +### Example 2: Analyze Contract + +**User Action**: Click "Analyze" button on uploaded contract +**Flow**: + +1. Status changes to PROCESSING with spinner +2. Toast appears: "⏳ Analyzing Contract" +3. AI analyzes the file +4. On Success: + - Toast: "βœ… Contract Analyzed" with details + - Database notification created + - Contract details populate +5. On Error: + - Toast: "❌ Analysis Failed" with reason + - Database notification created with error + - Invalid contract modal shown (if applicable) + +### Example 3: Delete Contract + +**User Action**: Click delete, confirm in dialog +**Flow**: + +1. Delete confirmation modal appears +2. On confirm: + - File deleted from UploadThing storage + - Contract deleted from database + - Toast: "πŸ—‘οΈ Contract Deleted" + - Notification created and stored +3. Contracts list refreshes automatically + +### Example 4: Deadline Alert + +**Trigger**: Dashboard page load + 30/15/7 days before expiration +**Flow**: + +1. System queries all user contracts with endDate +2. Calculates days until each expiration +3. For contracts expiring in 30, 15, or 7 days: + - Creates deadline notification + - Avoids duplicates (max 1 per threshold per day) +4. Notifications appear in bell icon dropdown +5. Toast displayed if first time that day + +## πŸ”” Notification Types & Colors + +| Type | Color | Icon | Use Case | +| -------- | --------- | ------------- | ------------------------------------ | +| SUCCESS | Green βœ… | CheckCircle2 | Contract uploaded, analyzed, deleted | +| ERROR | Red ❌ | AlertCircle | Upload failed, analysis failed | +| WARNING | Yellow ⚠️ | AlertTriangle | File taking long, low quality | +| INFO | Blue ℹ️ | Info | Processing started, general info | +| DEADLINE | Red πŸ• | Clock | Contract expiring soon | + +## πŸŽ›οΈ Notification Bar Features + +### When Closed + +- Shows bell icon +- Displays badge with unread count +- Pulses when unread notification arrives + +### When Open + +- Dropdown panel (w-96 max) +- Shows up to 15 most recent notifications +- Each notification shows: + - Type-specific icon and color + - Title and message + - Time (e.g., "2m ago") + - Contract link if available + - Unread indicator (red dot) + - Action buttons (βœ“ mark as read, πŸ—‘οΈ delete) +- "Mark all as read" button +- Empty state if no notifications + +### Auto-Refresh Behavior + +- Refreshes every 30 seconds when dropdown is open +- Checks for deadline notifications daily (24 hours) +- Silent refresh (doesn't show loading if already open) + +## πŸ” Security & Authorization + +All operations include authentication and authorization: + +1. **Clerk Authentication**: All actions verify user is logged in +2. **User Verification**: Notifications belong to authenticated user +3. **Contract Ownership**: Users only see their own contract notifications +4. **Server-Side Enforcement**: All operations run on server (no client manipulation) +5. **Database Constraints**: Foreign keys prevent orphaned records + +## βš™οΈ Configuration + +### Modify Deadline Thresholds + +Edit `lib/services/notification.service.ts`: + +```typescript +if (daysUntilExpiration === 7) { + // Change 7 to any number + shouldNotify = true; + level = "URGENT"; +} +``` + +### Change Default Expiration + +Edit `lib/services/notification.service.ts`: + +```typescript +const expiresAt = input.expiresIn + ? new Date(Date.now() + input.expiresIn) + : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // Change 30 to any days +``` + +### Adjust Polling Interval + +Edit `components/views/dashboard/notification-bar.tsx`: + +```typescript +const pollInterval = setInterval(fetchNotifications, 30000); // 30 seconds, change to any ms +``` + +## πŸ“± Database Schema + +### Notification Table + +```sql +CREATE TABLE "Notification" ( + id TEXT PRIMARY KEY, + userId TEXT NOT NULL, -- Link to User + contractId TEXT, -- Link to Contract (optional) + type NotificationType, -- SUCCESS, WARNING, ERROR, INFO, DEADLINE + title VARCHAR(255), -- e.g., "Contract Uploaded" + message TEXT, -- e.g., "insurance.pdf uploaded successfully" + icon VARCHAR(100), -- Lucide icon name for UI + actionType VARCHAR(100), -- e.g., "UPLOAD_SUCCESS", "RENEWAL_CRITICAL" + actionData JSONB, -- Additional metadata + read BOOLEAN DEFAULT false, -- Read status for badge + createdAt TIMESTAMP, -- When created + expiresAt TIMESTAMP, -- When notification expires + + FOREIGN KEY (userId) REFERENCES "User"(id) ON DELETE CASCADE, + FOREIGN KEY (contractId) REFERENCES "Contract"(id) ON DELETE SET NULL +); + +-- Indexes for fast queries +CREATE INDEX idx_userId ON "Notification"(userId); +CREATE INDEX idx_contractId ON "Notification"(contractId); +CREATE INDEX idx_type ON "Notification"(type); +CREATE INDEX idx_read ON "Notification"(read); +CREATE INDEX idx_createdAt ON "Notification"(createdAt DESC); +``` + +## πŸ§ͺ Testing the System + +### Manual Tests + +1. **Upload Notification** + - Go to /contacts + - Upload a contract + - Should see green toast: "πŸ“„ Contract Uploaded" + - Check notification bar - dot should appear + +2. **Analysis Notification** + - Click "Analyze" on uploaded contract + - Should see loading toast + - After 5-10 seconds, see success or error toast + - Check notification bar for detailed message + +3. **Delete Notification** + - Click delete on any contract + - Confirm in modal + - Should see toast: "πŸ—‘οΈ Contract Deleted" + +4. **Deadline Notification** + - Create a contract with endDate in 10 days + - Go to dashboard + - System automatically checks and creates notification + - See 🟑 URGENT notification in bell icon + +5. **Notification Bar Features** + - Click bell icon to open dropdown + - Click βœ“ to mark individual as read + - Click πŸ—‘οΈ to delete notification + - Click "Mark all as read" button + - Should auto-refresh when open + +## πŸ› Troubleshooting + +### Migration Fails + +```bash +# Solution 1: Check database connection +echo $DATABASE_URL + +# Solution 2: Reset migrations (dev only) +npx prisma migrate reset + +# Solution 3: Manually deploy +npx prisma migrate deploy +``` + +### Notifications Not Showing + +```bash +# Check 1: Verify notifications table exists +npx prisma db push + +# Check 2: Check database connection in server +# Open browser DevTools β†’ Network tab +# Look for failed API calls to notification endpoints + +# Check 3: Verify Prisma client is generated +npx prisma generate + +# Check 4: Rebuild project +npm run build +``` + +### Deadline Notifications Not Triggering + +``` +Check 1: Contract has endDate (not null) +Check 2: Contract status is "COMPLETED" (not UPLOADED/PROCESSING) +Check 3: Date is calculated correctly (midnight UTC) +Check 4: Manually trigger: await checkDeadlineNotifications() +``` + +### TypeScript Errors After Update + +```bash +npm run build +npx prisma generate +npm run dev +``` + +## πŸ“š Code Examples + +### Create Custom Notification in Server Action + +```typescript +import { NotificationService } from "@/lib/services/notification.service"; + +const result = await NotificationService.create({ + userId: user.id, + type: "SUCCESS", + title: "Custom Title", + message: "Custom message content", + contractId: contract.id, + actionType: "CUSTOM_ACTION", + expiresIn: 7 * 24 * 60 * 60 * 1000, // 7 days +}); +``` + +### Use Toast in Client Component + +```typescript +import { useNotifications } from "@/hooks/useNotifications"; +import { toast } from "sonner"; + +export function MyComponent() { + const { notifySuccess, notifyError } = useNotifications(); + + const handleClick = async () => { + try { + await someOperation(); + notifySuccess("Success!", "Operation completed"); + } catch (error) { + notifyError("Error!", error.message); + } + }; +} +``` + +### Check Notifications from Component + +```typescript +import { getNotifications } from "@/lib/actions/notification.action"; + +export async function NotificationPreview() { + const result = await getNotifications(10); + + if (result.success) { + console.log(result.data); // Array of notifications + } +} +``` + +## πŸŽ“ Learning Resources + +1. **Sonner Documentation**: https://sonner.emilkowal.ski/ +2. **Prisma Documentation**: https://www.prisma.io/docs/ +3. **Next.js Server Actions**: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions +4. **Shadcn/ui Components**: https://ui.shadcn.com/ + +## πŸš€ Future Enhancements + +Potential features to add: + +- [ ] Email notifications for deadline alerts +- [ ] Push notifications (Web/Mobile) +- [ ] Notification preferences (user can disable types) +- [ ] Snooze feature (temporarily hide notifications) +- [ ] Advanced filtering (by type, date, contract) +- [ ] Bulk operations (mark all, delete all) +- [ ] Export notification history (CSV) +- [ ] Recurring reminders if ignored +- [ ] Notification sounds/vibrations +- [ ] Smart digest (combine similar notifications) + +## πŸ“ž Support + +For issues or questions: + +1. Check error console (browser DevTools) +2. Review NOTIFICATION_SYSTEM_SETUP.md +3. Check notification-bar.tsx for UI implementation +4. See lib/services/notification.service.ts for core logic +5. Review contracts-list.tsx for toast integration examples + +## βœ… Checklist - Setup Verification + +After setup, verify: + +- [ ] Database migration completed successfully +- [ ] Prisma client generated +- [ ] No TypeScript errors in build +- [ ] NotificationBar visible in dashboard sidebar +- [ ] Upload creates notification toast +- [ ] Analysis creates notification toast +- [ ] Delete creates notification toast +- [ ] Notification bar bell icon shows unread count +- [ ] Notification dropdown opens/closes smoothly +- [ ] Can mark notifications as read/delete +- [ ] Deadline notifications appear for contracts expiring in 30/15/7 days +- [ ] Auto-refresh works when dropdown is open + +## πŸŽ‰ You're All Set! + +The notification system is now fully integrated with: + +- βœ… Sonner toast notifications for immediate feedback +- βœ… Persistent database notifications for history +- βœ… Beautiful notification bar UI with unread badge +- βœ… Automatic deadline detection and alerts +- βœ… Well-documented, production-ready code + +Start uploading contracts and watch the notifications come to life! diff --git a/NOTIFICATION_SYSTEM_SETUP.md b/NOTIFICATION_SYSTEM_SETUP.md new file mode 100644 index 0000000..5a99bca --- /dev/null +++ b/NOTIFICATION_SYSTEM_SETUP.md @@ -0,0 +1,386 @@ +# πŸ”” Notification System Setup Guide + +## Overview + +The notification system has been implemented to notify users about: + +- βœ… **Action Notifications**: When users upload, analyze, or delete contracts +- πŸ• **Deadline Notifications**: When contracts are expiring (30, 15, 7 days) +- πŸ“± **Toast Notifications**: Immediate UI feedback for all actions +- πŸ”” **Notification Center**: Persistent notification history accessible from the dashboard + +## Database Migration + +### Step 1: Update Your Database Schema + +Run the following Prisma command to create the necessary database tables: + +```bash +npx prisma migrate dev --name add_notifications +``` + +This will: + +1. Create the `Notification` table +2. Create the `NotificationType` enum +3. Add the `notifications` relationship to the `User` model +4. Add the `notifications` relationship to the `Contract` model + +### Step 2: Database Schema Overview + +The migration adds: + +```sql +-- Notification table with indexes +CREATE TABLE "Notification" ( + id TEXT PRIMARY KEY, + userId TEXT NOT NULL, + contractId TEXT, + type "NotificationType" NOT NULL, + title VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + icon VARCHAR(100), + actionType VARCHAR(100), + actionData JSONB, + read BOOLEAN DEFAULT false, + createdAt TIMESTAMP DEFAULT now(), + expiresAt TIMESTAMP, + FOREIGN KEY (userId) REFERENCES "User"(id) ON DELETE CASCADE, + FOREIGN KEY (contractId) REFERENCES "Contract"(id) ON DELETE SET NULL +); + +-- Notification Type Enum +CREATE TYPE "NotificationType" AS ENUM ( + 'SUCCESS', + 'WARNING', + 'ERROR', + 'INFO', + 'DEADLINE' +); +``` + +## Architecture Overview + +### 1. **Notification Service** (`lib/services/notification.service.ts`) + +Core service handling all notification operations: + +- Create notifications +- Fetch unread/all notifications +- Mark as read +- Delete notifications +- Check for upcoming deadlines +- Cleanup expired notifications + +**Key Methods:** + +- `create(input)` - Create new notification +- `getUnread(userId, limit)` - Fetch unread notifications +- `getUnreadCount(userId)` - Get badge count +- `checkUpcomingDeadlines(userId)` - Scan contracts and create deadline notifications +- `cleanupExpired()` - Remove expired notifications (run periodically) + +### 2. **Notification Actions** (`lib/actions/notification.action.ts`) + +Server actions for client-side notification management: + +- `getNotifications()` - Fetch unread notifications +- `getAllNotifications()` - Fetch notification history +- `getUnreadNotificationCount()` - Get badge count +- `markNotificationAsRead(id)` - Mark single as read +- `markAllNotificationsAsRead()` - Mark all as read +- `deleteNotification(id)` - Delete notification +- `checkDeadlineNotifications()` - Check and create deadline notifications + +### 3. **Notification Component** (`components/views/dashboard/notification-bar.tsx`) + +Beautiful notification UI dropdown with: + +- Bell icon with unread count badge +- Notification list with type-specific icons and colors +- Action buttons (mark as read, delete) +- Time formatting (e.g., "2m ago") +- Empty state +- Auto-refresh every 30 seconds when open +- Daily deadline check + +### 4. **useNotifications Hook** (`hooks/useNotifications.ts`) + +Custom React hook wrapping Sonner toast + persistent notifications: + +- `notifySuccess()` - Green success toast + persistent notification +- `notifyError()` - Red error toast + persistent notification +- `notifyWarning()` - Yellow warning toast + persistent notification +- `notifyInfo()` - Blue info toast + persistent notification +- `notifyDeadline()` - Red deadline toast + persistent notification + +## Integration Points + +### 1. **Contract Actions** (`lib/actions/contract.action.ts`) + +Updated to create notifications on: + +- βœ… **Upload**: "Contract uploaded successfully" +- βœ… **Analysis**: "Contract analyzed successfully" or error message +- βœ… **Delete**: "Contract deleted successfully" +- βœ… **Ask Question**: Error notification if Q&A fails + +### 2. **Dashboard** (`app/(dashboard)/dashboard/page.tsx`) + +- Calls `checkDeadlineNotifications()` on page load +- Checks for contracts expiring in 30, 15, 7 days +- Creates notifications automatically + +### 3. **Navigation** (`components/views/dashboard/navigation.tsx`) + +- Added `NotificationBar` component to sidebar +- Positioned next to theme toggle in account section +- Accessible from all dashboard pages + +## Usage Examples + +### Creating a Notification in Server Actions + +```typescript +import { NotificationService } from "@/lib/services/notification.service"; + +// In a server action +const user = await ContractService.getUserByClerkId(clerkId); + +await NotificationService.create({ + userId: user.id, + type: "SUCCESS", + title: "Contract Uploaded", + message: 'Your file "insurance.pdf" is ready for analysis', + contractId: contractId, + actionType: "UPLOAD_SUCCESS", + icon: "FileCheck", + expiresIn: 7 * 24 * 60 * 60 * 1000, // 7 days +}); +``` + +### Using Toast Notifications in Client Components + +```typescript +import { useNotifications } from "@/hooks/useNotifications"; + +export function MyComponent() { + const { notifySuccess, notifyError, notifyDeadline } = useNotifications(); + + const handleUpload = async () => { + try { + await uploadFile(); + notifySuccess("Upload Complete", "Your file has been uploaded"); + } catch (error) { + notifyError("Upload Failed", error.message); + } + }; + + const handleDeadline = () => { + notifyDeadline({ + title: "πŸ”΄ Contract Expiring Soon", + message: "Insurance Auto from ACME expires in 7 days", + contractId: "contract123", + actionType: "RENEWAL_URGENT", + }); + }; + + return ( + <> + + + + ); +} +``` + +## Notification Types + +### SUCCESS (Green - βœ…) + +Used for: + +- Contract uploaded +- Analysis completed +- Contract deleted +- Settings saved + +### ERROR (Red - ❌) + +Used for: + +- Upload failed +- Analysis failed +- Network errors +- Invalid contract + +### WARNING (Yellow - ⚠️) + +Used for: + +- File analysis slow +- Low quality extraction +- Missing permissions + +### INFO (Blue - ℹ️) + +Used for: + +- Analysis started +- Processing updates +- General information + +### DEADLINE (Red - πŸ•) + +Used for: + +- Contract expiring in 30 days (CRITICAL πŸ”΄) +- Contract expiring in 15 days (WARNING 🟠) +- Contract expiring in 7 days (URGENT 🟑) + +## Scheduled Tasks + +### Daily Deadline Check + +The system checks for upcoming deadlines: + +- **When**: Daily at any time (triggered on dashboard load) +- **What**: Scans all user contracts with endDate +- **Actions**: + - Creates CRITICAL notification for 30-day threshold + - Creates WARNING notification for 15-day threshold + - Creates URGENT notification for 7-day threshold + - Avoids duplicate notifications (max 1 per threshold per day) + +### Running Deadline Checks Manually + +```typescript +import { checkDeadlineNotifications } from "@/lib/actions/notification.action"; + +// Client-side +const result = await checkDeadlineNotifications(); +console.log(`Created ${result.data.count} deadline notifications`); +``` + +### Cleanup Task (Recommended) + +For production, set up a cron job to clean expired notifications: + +```typescript +import { NotificationService } from "@/lib/services/notification.service"; + +// Example: Vercel Cron (serverless function every day at midnight) +export async function GET(request: Request) { + const authHeader = request.headers.get("authorization"); + if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { + return new Response("Unauthorized", { status: 401 }); + } + + const result = await NotificationService.cleanupExpired(); + return Response.json(result); +} + +// Route: api/cron/cleanup-notifications +// Set Cron Job: 0 0 * * * (daily at midnight) +``` + +## Configurable Parameters + +### Default Notification Expiration + +```typescript +// In NotificationService.create() +const expiresAt = input.expiresIn + ? new Date(Date.now() + input.expiresIn) + : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days default +``` + +### Deadline Thresholds + +Located in `NotificationService.checkUpcomingDeadlines()`: + +- 30 days: CRITICAL level +- 15 days: WARNING level +- 7 days: URGENT level + +To modify, edit these lines in `notification.service.ts`: + +```typescript +if (daysUntilExpiration === 7) { + /* ... */ +} else if (daysUntilExpiration === 15) { + /* ... */ +} else if (daysUntilExpiration === 30) { + /* ... */ +} +``` + +### Notification Bar Polling + +```typescript +// In notification-bar.tsx +const pollInterval = setInterval(fetchNotifications, 30000); // 30 seconds +const dailyCheckInterval = setInterval( + () => { + checkDeadlineNotifications(); + }, + 24 * 60 * 60 * 1000, +); // 24 hours +``` + +## Security & Authorization + +All notification operations include authorization checks: + +1. **User Verification**: Each action verifies the user owns the notification +2. **Contract Ownership**: Deadline checks only process user's contracts +3. **Input Validation**: Notification content isn't sanitized (all text) +4. **Server-Side Enforcement**: All operations run server-side (no client-side manipulation) + +## Troubleshooting + +### Migrations Don't Apply + +```bash +# Reset migrations (for development only) +npx prisma migrate reset + +# Or manually apply pending migrations +npx prisma migrate deploy +``` + +### Notifications Not Showing + +1. Check database connection +2. Verify user exists in User table +3. Check browser console for errors +4. Verify `checkDeadlineNotifications` was called + +### Deadline Notifications Not Triggering + +1. Ensure contract has `endDate` set +2. Verify contract status is "COMPLETED" +3. Check date calculation (today at midnight) +4. Run manual check: `await checkDeadlineNotifications()` + +## Future Enhancements + +Potential improvements: + +1. **Email Notifications**: Send deadline alerts via email +2. **Batch Operations**: Bulk mark/delete notifications +3. **Notification Preferences**: Let users disable certain types +4. **Snooze Feature**: Temporarily hide notifications +5. **Advanced Filtering**: Filter by type, date range, contract +6. **Export History**: Download notification log as CSV +7. **Push Notifications**: Web/mobile push for critical alerts +8. **Recurring Reminders**: Repeat deadline notifications if ignored + +## Support + +For issues or questions: + +1. Check the notification service comments for implementation details +2. Review contracts-list.tsx for toast integration examples +3. Check notification-bar.tsx for UI implementation +4. See hooks/useNotifications.ts for hook usage diff --git a/PROJECT_ARCHITECTURE_AND_STACK.md b/PROJECT_ARCHITECTURE_AND_STACK.md new file mode 100644 index 0000000..519d03c --- /dev/null +++ b/PROJECT_ARCHITECTURE_AND_STACK.md @@ -0,0 +1,208 @@ +# Project Architecture & Technology Stack + +**Project:** LexiChain (BFSI Contract Intelligence Platform) +**Date:** March 8, 2026 +**Scope:** Rationale for choosing Next.js, current stack snapshot, and end-to-end communication architecture. + +## 1) Why We Chose Next.js + +We selected **Next.js** because it aligns with the exact technical and product needs of this project: + +1. **Full-stack in one framework** + The project combines UI, backend logic, and API endpoints in a single codebase (`app` routes, server actions, route handlers). This reduces context switching and speeds up delivery. + +2. **App Router structure for product domains** + Route groups like `(auth)` and `(dashboard)` map directly to business areas, making navigation and ownership clear. + +3. **Server Actions for secure business operations** + Operations such as saving contracts, analysis triggers, and fetching statistics are executed server-side (`"use server"`), which keeps privileged logic off the client. + +4. **Built-in route handlers for integrations** + Integrations such as Clerk webhooks and UploadThing endpoints are implemented natively in `app/api/*`, with no separate backend server required. + +5. **Strong authentication integration** + Clerk works naturally with Next.js server functions (`auth()`), middleware/proxy route protection, and auth UI pages. + +6. **Performance and UX benefits** + Next.js supports hybrid rendering and optimized routing while allowing rich client interactivity where needed (dashboard, charts, upload flows). + +7. **Excellent TypeScript compatibility** + The project uses strict TypeScript with shared models and service layers, improving reliability in a data-sensitive BFSI context. + +--- + +## 2) Current Stack (As Implemented) + +### Core Platform +- **Next.js:** `16.1.6` +- **React:** `19.2.3` +- **TypeScript:** `5.x` (strict mode) +- **Node/NPM scripts:** standard `dev`, `build`, `start`, `lint` + +### UI & Frontend +- **Tailwind CSS** + `tailwindcss-animate` +- **shadcn/ui** (New York style) built on **Radix UI** primitives +- **Lucide React** icons +- **Motion** (`motion/react`) for animations +- **Recharts** for analytics visualizations +- **Sonner** for toast notifications +- **next-themes** for dark/light mode + +### Authentication & Access Control +- **Clerk** (`@clerk/nextjs`, `@clerk/themes`) +- Route protection through `proxy.ts` using Clerk middleware +- Auth flows with Clerk `SignIn` and `SignUp` pages +- Webhook verification using **Svix** for secure user lifecycle sync + +### Data Layer +- **PostgreSQL** via **Prisma ORM** (`@prisma/client`, `prisma`) +- Main entities: `User` and `Contract` +- Enum-based domain modeling (`ContractType`, `ContractStatus`) + +### File Ingestion & Storage +- **UploadThing** (`uploadthing`, `@uploadthing/react`) +- Controlled file constraints and authenticated upload middleware +- Upload completion callback integrated with contract persistence + +### AI & Contract Intelligence +- **Google Generative AI** SDK (`@google/generative-ai`) +- Gemini model (`gemini-2.5-flash`) for: + - contract extraction, + - structured summary generation, + - key-point generation, + - contract Q&A. + +### Validation / Forms / Utility +- **react-hook-form**, **zod**, `@hookform/resolvers` +- Utility stack: `clsx`, `class-variance-authority`, `tailwind-merge`, etc. + +--- + +## 3) Architecture Overview + +The app follows a **layered Next.js architecture** with clear responsibilities: + +1. **Presentation Layer (Client Components / Views)** + - Route UI in `app/*` + - Reusable view modules in `components/views/*` + - Design-system components in `components/ui/*` + +2. **Application Layer (Server Actions)** + - `lib/actions/*` + - Entry point for authenticated server-side operations initiated by the UI + +3. **Domain/Service Layer** + - `lib/services/*` + - Encapsulates business logic (contracts, AI analysis, stats, storage helpers) + +4. **Persistence Layer** + - `lib/db/prisma.ts` + `prisma/schema.prisma` + - Database operations and schema modeling + +5. **Integration Layer (External Systems)** + - Route handlers in `app/api/*` (UploadThing, Clerk webhooks) + - External providers: Clerk, UploadThing, Gemini API + +### High-Level Component Map +- **Public Marketing Experience:** `app/page.tsx` + `components/views/Home/*` +- **Authenticated Workspace:** `app/(dashboard)/*` + dashboard navigation/layout +- **Document Operations:** upload form, contract list, AI actions, analytics charts + +--- + +## 4) How Each Part Communicates + +### A. Authentication and Access +1. User requests a protected page. +2. `proxy.ts` (Clerk middleware) checks route rules and enforces authentication. +3. Inside server layouts/actions, `auth()` verifies user identity again for operation-level security. +4. Clerk webhook events (`app/api/webhooks/clerk/route.ts`) synchronize user records into PostgreSQL. + +**Result:** route-level and operation-level protection, plus identity consistency between Clerk and local DB. + +### B. Contract Upload and Persistence +1. User uploads a file from `ContractUploadForm` (client). +2. Upload is sent to UploadThing endpoint (`/api/uploadthing`). +3. UploadThing middleware validates authenticated user. +4. On completion, client calls server action `saveContract`. +5. Server action delegates to `ContractService.create`. +6. Service resolves local user via `clerkId` and inserts `Contract` record with status lifecycle. +7. Paths are revalidated so UI refreshes with latest data. + +**Result:** secure file ingestion + persistent contract metadata in one flow. + +### C. Contract Analysis (AI Enrichment) +1. User clicks β€œAnalyze” in contracts list. +2. Client triggers `analyzeContractAction` (server action). +3. Action validates auth, fetches contract, sets status to `PROCESSING`. +4. `AIService` downloads file URL and sends content to Gemini. +5. AI JSON output is validated and normalized. +6. `ContractService.updateWithAIResults` saves extracted fields (`summary`, `keyPoints`, type, dates, premium, etc.) and marks status `COMPLETED`. +7. Failures set status to `FAILED`. + +**Result:** contract moves from raw upload to structured intelligence record. + +### D. Analytics and Dashboard Data +1. Dashboard UI calls `getStatsAction`. +2. Action validates user and calls `stats.service`. +3. Service runs Prisma aggregations/groupings. +4. Data is returned as dashboard KPIs + chart-ready payloads. +5. Recharts renders trend/type/status analytics on the client. + +**Result:** real-time operational visibility from server-computed metrics. + +### E. Contract Q&A +1. User asks question in contract context. +2. Client calls `askContractQuestionAction`. +3. Server action fetches contract + analysis context. +4. `AIService.askAboutContract` sends prompt + metadata/extracted content to Gemini. +5. Sanitized answer returns to chat UI. + +**Result:** contextual AI assistant grounded in analyzed contract data. + +--- + +## 5) Runtime Communication Diagram + +```mermaid +flowchart TD + U[User Browser] --> UI[Next.js App Router UI] + + UI -->|Server Actions| SA[lib/actions] + SA --> SVC[lib/services] + SVC --> DB[(PostgreSQL via Prisma)] + + UI -->|Upload| UT[UploadThing] + UT -->|Upload Callback Data| SA + + SVC -->|Analyze / Q&A| AI[Google Gemini API] + + Clerk[Clerk Auth] -->|Session/Auth| UI + Clerk -->|Webhooks via Svix| WH[app/api/webhooks/clerk] + WH --> DB + + Proxy[proxy.ts Clerk Middleware] --> UI +``` + +--- + +## 6) Architecture Characteristics + +### Strengths +- **Single cohesive full-stack codebase** with clear file-system boundaries +- **Fast feature delivery** using App Router + server actions +- **Good security baseline** (middleware + server auth checks + webhook verification) +- **Strong type safety** across UI, actions, services, and data models +- **Integration-ready design** for AI/document workflows + +### Current Maturity Notes +- AI execution is implemented server-side and can be triggered from user workflow. +- Existing comments already indicate a future path for queue-based background orchestration in production-scale scenarios. + +--- + +## 7) Conclusion + +Next.js is the right foundation for this BFSI platform because it enables secure, full-stack product development in one architecture: rich UI, authenticated server operations, API integrations, and data persistence. + +The current stack is modern, production-oriented, and well-aligned with the project goals: **contract ingestion, AI enrichment, analytics visibility, and secure user-scoped access**. \ No newline at end of file diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx new file mode 100644 index 0000000..3b12e5d --- /dev/null +++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx @@ -0,0 +1,33 @@ +// The [[...sign-in]] folder name is MAGIC! +// It catches all Clerk's internal sign-in routes +// (sign-in, sign-in/factor-one, sign-in/sso-callback, etc.) + +import { SignIn } from "@clerk/nextjs"; + +export default function SignInPage() { + return ( +
+ {/* Left side - branding (optional, add later) */} +
+ +
+
+ ); +} diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx new file mode 100644 index 0000000..9c0c76a --- /dev/null +++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx @@ -0,0 +1,27 @@ +import { SignUp } from "@clerk/nextjs"; + +export default function SignUpPage() { + return ( +
+
+ +
+
+ ); +} diff --git a/app/(dashboard)/contacts/layout.tsx b/app/(dashboard)/contacts/layout.tsx new file mode 100644 index 0000000..62d81ec --- /dev/null +++ b/app/(dashboard)/contacts/layout.tsx @@ -0,0 +1,16 @@ +import { redirect } from "next/navigation"; +import { auth } from "@clerk/nextjs/server"; + +export default async function ContactsLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { userId } = await auth(); + + if (!userId) { + redirect("/sign-in"); + } + + return <>{children}; +} diff --git a/app/(dashboard)/contacts/page.tsx b/app/(dashboard)/contacts/page.tsx new file mode 100644 index 0000000..9fbace6 --- /dev/null +++ b/app/(dashboard)/contacts/page.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { ContractUploadForm } from "@/components/views/dashboard/contract-upload-form"; +import { EmptyContractsState } from "@/components/views/dashboard/empty-contracts-state"; +import { ContractsList } from "@/components/views/dashboard/contracts-list"; +import { ContactsHeader } from "@/components/views/dashboard/contacts-header"; +import { useState, useEffect } from "react"; +import { getContracts } from "@/lib/actions/contract.action"; +import { Card } from "@/components/ui/card"; + +export default function ContactsPage() { + const [refreshTrigger, setRefreshTrigger] = useState(0); + const [showContracts, setShowContracts] = useState(false); + const [isChecking, setIsChecking] = useState(true); + + // Check if there are any existing contracts on mount + useEffect(() => { + const checkContracts = async () => { + try { + const result = await getContracts(); + if ( + result.success && + Array.isArray(result.contracts) && + result.contracts.length > 0 + ) { + setShowContracts(true); + } + } catch (error) { + console.error("Failed to check contracts:", error); + } finally { + setIsChecking(false); + } + }; + + checkContracts(); + }, []); + + const handleUploadSuccess = () => { + setRefreshTrigger((prev) => prev + 1); + setShowContracts(true); + }; + + if (isChecking) { + return ( + <> +
+
+
+
+
+
+ +
+
+
+
+
+

Loading...

+
+
+
+ + ); + } + + return ( + <> +
+
+ + +
+
+ +
+

+ Upload Contract +

+

+ Add PDF contracts and let the AI pipeline extract summary, + key points, and legal-business insights. +

+
+ +
+ + +
+

+ Your Contracts +

+

+ Review contract lifecycle, trigger analysis, and ask AI + questions per file. +

+
+ + {showContracts ? ( + + ) : ( + + )} +
+
+
+
+
+ + ); +} diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx new file mode 100644 index 0000000..53f483e --- /dev/null +++ b/app/(dashboard)/dashboard/page.tsx @@ -0,0 +1,814 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import Link from "next/link"; +import { motion } from "motion/react"; +import { + Activity, + AlertTriangle, + ArrowRight, + BarChart3, + Brain, + CheckCircle2, + Clock3, + Database, + FileText, + RefreshCw, + Sparkles, + TrendingUp, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { getStatsAction } from "@/lib/actions/stats.action"; +import { checkDeadlineNotifications } from "@/lib/actions/notification.action"; +import { + ContractStatusChart, + ContractTypeChart, + TrendChart, +} from "@/components/views/dashboard/charts"; + +interface DashboardStats { + totalContracts: number; + analyzedContracts: number; + processingContracts: number; + uploadedContracts: number; + failedContracts: number; + analysisRate: number; +} + +interface ChartData { + byType: Array<{ type: string; count: number }>; + byStatus: Array<{ status: string; count: number }>; + trends: Array<{ date: string; count: number }>; +} + +interface PremiumInfo { + averagePremium: number; + totalPremium: number; + count: number; +} + +interface RecentContract { + id: string; + title: string | null; + type: string | null; + createdAt: string; + premium: number | null; +} + +interface AILearningTelemetry { + completedSamples: number; + completedLast7Days: number; + avgSummaryLength: number; + avgExtractedTextLength: number; + avgKeyPointsPerContract: number; + learningScore: number; + improvementHint: string; +} + +interface StatsActionResult { + success: boolean; + stats?: DashboardStats; + chartData?: ChartData; + premiumInfo?: PremiumInfo; + aiLearningTelemetry?: AILearningTelemetry; + recentContracts?: RecentContract[]; + error?: string; +} + +const numberFormatter = new Intl.NumberFormat("en-US"); + +const currencyFormatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, +}); + +const defaultStats: DashboardStats = { + totalContracts: 0, + analyzedContracts: 0, + processingContracts: 0, + uploadedContracts: 0, + failedContracts: 0, + analysisRate: 0, +}; + +const formatLastUpdated = (date: Date | null): string => { + if (!date) { + return "Just now"; + } + + const seconds = Math.max(1, Math.floor((Date.now() - date.getTime()) / 1000)); + if (seconds < 60) return `${seconds}s ago`; + + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + + const hours = Math.floor(minutes / 60); + return `${hours}h ago`; +}; + +const clampPercent = (value: number): number => + Math.max(0, Math.min(100, value)); + +export default function DashboardPage() { + const [stats, setStats] = useState(defaultStats); + const [chartData, setChartData] = useState(null); + const [premiumInfo, setPremiumInfo] = useState({ + averagePremium: 0, + totalPremium: 0, + count: 0, + }); + const [recentContracts, setRecentContracts] = useState([]); + const [aiLearningTelemetry, setAiLearningTelemetry] = + useState({ + completedSamples: 0, + completedLast7Days: 0, + avgSummaryLength: 0, + avgExtractedTextLength: 0, + avgKeyPointsPerContract: 0, + learningScore: 0, + improvementHint: "Analyze contracts to build your AI quality profile.", + }); + const [isLoading, setIsLoading] = useState(true); + const [isRefreshing, setIsRefreshing] = useState(false); + const [lastUpdated, setLastUpdated] = useState(null); + + const loadStats = useCallback(async (options?: { silent?: boolean }) => { + const isSilentRefresh = options?.silent ?? false; + + if (isSilentRefresh) { + setIsRefreshing(true); + } else { + setIsLoading(true); + } + + try { + const result = (await getStatsAction()) as StatsActionResult; + + if (!result.success) { + return; + } + + if (result.stats) setStats(result.stats); + if (result.chartData) setChartData(result.chartData); + if (result.premiumInfo) setPremiumInfo(result.premiumInfo); + if (result.aiLearningTelemetry) { + setAiLearningTelemetry(result.aiLearningTelemetry); + } + if (result.recentContracts) setRecentContracts(result.recentContracts); + + setLastUpdated(new Date()); + } finally { + if (isSilentRefresh) { + setIsRefreshing(false); + } else { + setIsLoading(false); + } + } + }, []); + + useEffect(() => { + void loadStats(); + // Check for upcoming contract deadlines and create notifications + void checkDeadlineNotifications(); + }, [loadStats]); + + useEffect(() => { + if (stats.processingContracts === 0 && stats.uploadedContracts === 0) { + return; + } + + const intervalId = window.setInterval(() => { + void loadStats({ silent: true }); + }, 10000); + + return () => window.clearInterval(intervalId); + }, [loadStats, stats.processingContracts, stats.uploadedContracts]); + + const hasChartData = useMemo(() => { + if (!chartData) return false; + + const trendsCount = chartData.trends.reduce( + (total, entry) => total + entry.count, + 0, + ); + const byStatusCount = chartData.byStatus.reduce( + (total, entry) => total + entry.count, + 0, + ); + const byTypeCount = chartData.byType.reduce( + (total, entry) => total + entry.count, + 0, + ); + + return trendsCount + byStatusCount + byTypeCount > 0; + }, [chartData]); + + const pendingContracts = stats.processingContracts + stats.uploadedContracts; + + const analyzedPercent = + stats.totalContracts > 0 + ? clampPercent((stats.analyzedContracts / stats.totalContracts) * 100) + : 0; + + const pendingPercent = + stats.totalContracts > 0 + ? clampPercent((pendingContracts / stats.totalContracts) * 100) + : 0; + + const failedPercent = + stats.totalContracts > 0 + ? clampPercent((stats.failedContracts / stats.totalContracts) * 100) + : 0; + + const statusRows = [ + { + label: "Uploaded", + value: stats.uploadedContracts, + colorClass: "bg-amber-500", + }, + { + label: "Processing", + value: stats.processingContracts, + colorClass: "bg-primary", + }, + { + label: "Analyzed", + value: stats.analyzedContracts, + colorClass: "bg-emerald-500", + }, + { + label: "Failed", + value: stats.failedContracts, + colorClass: "bg-destructive", + }, + ]; + + if (isLoading) { + return ( +
+
+ +
+ + Building your analytics workspace... +
+
+
+
+ ); + } + + return ( +
+
+
+ +
+ +
+

+ + Performance Overview +

+

+ Financial Contracts Analytics +

+

+ A reliable command center for uploaded documents, AI analysis + throughput, and portfolio quality across your BFSI workflow. +

+ +
+ + + Live metrics + + + + {isRefreshing + ? "Syncing..." + : `Updated ${formatLastUpdated(lastUpdated)}`} + +
+
+ + +
+
+

+ Pipeline Snapshot +

+

+ {numberFormatter.format(stats.totalContracts)} files +

+
+
+ +
+
+ +
+ +
+ +
+
+

Analyzed

+

+ {numberFormatter.format(stats.analyzedContracts)} +

+
+
+

Pending

+

+ {numberFormatter.format(pendingContracts)} +

+
+
+ +
+ + +
+
+
+
+
+ +
+
+ +
+

Total Files

+ +
+

+ {numberFormatter.format(stats.totalContracts)} +

+

+ Uploaded into your workspace +

+
+ + +
+

Analyzed

+ +
+

+ {numberFormatter.format(stats.analyzedContracts)} +

+

+ Completed by AI pipeline +

+
+
+
+ + + +
+

Pending Queue

+ +
+

+ {numberFormatter.format(pendingContracts)} +

+

+ Uploaded and processing files +

+
+
+
+ + + +
+

Failed

+ +
+

+ {numberFormatter.format(stats.failedContracts)} +

+

+ Items needing re-analysis +

+
+
+
+ +
+ + +
+
+
+ +

Pipeline Pulse

+
+
+ {statusRows.map((row) => { + const rowPercent = + stats.totalContracts > 0 + ? clampPercent((row.value / stats.totalContracts) * 100) + : 0; + + return ( +
+
+

{row.label}

+

+ {numberFormatter.format(row.value)} +

+
+
+
+
+
+ ); + })} +
+
+ +
+
+

Success Rate

+

+ {stats.analysisRate}% +

+

+ Completed vs total files +

+
+
+

Avg Premium

+

+ {currencyFormatter.format(premiumInfo.averagePremium)} +

+

+ Across {numberFormatter.format(premiumInfo.count)} analyzed + files +

+
+
+

Total Premium

+

+ {currencyFormatter.format(premiumInfo.totalPremium)} +

+

+ Portfolio value captured by AI +

+
+
+
+ + + +
+
+ +

AI Learning Telemetry

+
+ + + Score {aiLearningTelemetry.learningScore}/100 + +
+ +
+
+

Completed Samples

+

+ {numberFormatter.format(aiLearningTelemetry.completedSamples)} +

+

+ {numberFormatter.format(aiLearningTelemetry.completedLast7Days)}{" "} + in last 7 days +

+
+ +
+

+ Avg Summary Length +

+

+ {numberFormatter.format(aiLearningTelemetry.avgSummaryLength)} +

+

characters

+
+ +
+

+ Avg Extracted Text +

+

+ {numberFormatter.format( + aiLearningTelemetry.avgExtractedTextLength, + )} +

+

characters

+
+ +
+

Avg Key Points

+

+ {aiLearningTelemetry.avgKeyPointsPerContract.toFixed(1)} +

+

+ items per analysis +

+
+
+ +
+
+ Learning quality index + {aiLearningTelemetry.learningScore}% +
+
+
+
+

+ {aiLearningTelemetry.improvementHint} +

+
+ + + {hasChartData ? ( +
+ {chartData && chartData.trends.length > 0 && ( + +
+ +

+ Upload Trend (30 days) +

+
+
+ +
+
+ )} + + {chartData && chartData.byStatus.length > 0 && ( + +
+ +

Processing Status

+
+
+ ({ + ...s, + name: s.status, + }))} + /> +
+
+ )} + + {chartData && chartData.byType.length > 0 && ( + +
+ +

+ Contract Type Distribution +

+
+
+ +
+
+ )} + + +
+ +

Recent Analyses

+
+ + {recentContracts.length > 0 ? ( +
+ {recentContracts.map((contract) => ( +
+

+ {contract.title || "Untitled contract"} +

+
+ {contract.type || "Unknown type"} + + {new Date(contract.createdAt).toLocaleDateString( + "en-US", + { + month: "short", + day: "numeric", + }, + )} + +
+

+ Premium:{" "} + {contract.premium !== null + ? currencyFormatter.format(contract.premium) + : "Not detected"} +

+
+ ))} +
+ ) : ( +
+
+

+ No recent analyses yet +

+

+ Analyze a contract to populate this activity feed. +

+
+
+ )} +
+
+ ) : ( + +
+ + + + + + + +

+ Your analytics will appear here +

+

+ Upload and analyze contracts to unlock trend and distribution + charts. +

+ +
+
+ )} +
+
+ ); +} diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..3e279a1 --- /dev/null +++ b/app/(dashboard)/layout.tsx @@ -0,0 +1,22 @@ +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; +import { DashboardNavigation } from "@/components/views/dashboard/navigation"; + +export default async function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { userId } = await auth(); + + if (!userId) { + redirect("/sign-in"); + } + + return ( +
+ +
{children}
+
+ ); +} diff --git a/app/api/uploadthing/core.ts b/app/api/uploadthing/core.ts new file mode 100644 index 0000000..ed11c49 --- /dev/null +++ b/app/api/uploadthing/core.ts @@ -0,0 +1,8 @@ +// src/app/api/uploadthing/core.ts + +import { createRouteHandler } from "uploadthing/next"; +import { ourFileRouter } from "@/lib/upload"; + +export const { GET, POST } = createRouteHandler({ + router: ourFileRouter, +}); diff --git a/app/api/uploadthing/route.ts b/app/api/uploadthing/route.ts new file mode 100644 index 0000000..835d68e --- /dev/null +++ b/app/api/uploadthing/route.ts @@ -0,0 +1,3 @@ +// src/app/api/uploadthing/route.ts + +export { GET, POST } from "./core"; diff --git a/app/api/webhooks/clerk/route.ts b/app/api/webhooks/clerk/route.ts new file mode 100644 index 0000000..cd8ccb2 --- /dev/null +++ b/app/api/webhooks/clerk/route.ts @@ -0,0 +1,164 @@ +// src/app/api/webhooks/clerk/route.ts + +import { Webhook } from "svix"; +import { headers } from "next/headers"; +import { WebhookEvent } from "@clerk/nextjs/server"; +import { prisma } from "@/lib/db/prisma"; + +export async function POST(req: Request) { + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // STEP 1: Get the webhook secret from environment + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET; + + if (!WEBHOOK_SECRET) { + console.error("❌ Missing CLERK_WEBHOOK_SECRET in environment variables"); + return new Response("Server configuration error", { status: 500 }); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // STEP 2: Get headers needed for verification + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const headerPayload = await headers(); + const svix_id = headerPayload.get("svix-id"); + const svix_timestamp = headerPayload.get("svix-timestamp"); + const svix_signature = headerPayload.get("svix-signature"); + + // If there are no headers, error out + if (!svix_id || !svix_timestamp || !svix_signature) { + console.error("❌ Missing svix headers"); + return new Response("Missing svix headers", { status: 400 }); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // STEP 3: Get the request body + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const payload = await req.json(); + const body = JSON.stringify(payload); + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // STEP 4: Verify the webhook signature + // This ensures the webhook is actually from Clerk + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const wh = new Webhook(WEBHOOK_SECRET); + let evt: WebhookEvent; + + try { + evt = wh.verify(body, { + "svix-id": svix_id, + "svix-timestamp": svix_timestamp, + "svix-signature": svix_signature, + }) as WebhookEvent; + } catch (err) { + console.error("❌ Webhook verification failed:", err); + return new Response("Invalid signature", { status: 400 }); + } + + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + // STEP 5: Handle different webhook events + // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + const eventType = evt.type; + + console.log(`πŸ“₯ Webhook received: ${eventType}`); + + switch (eventType) { + // ═══════════════════════════════════════════════════ + // USER CREATED + // ═══════════════════════════════════════════════════ + case "user.created": { + const { id, email_addresses, first_name, last_name, image_url } = + evt.data; + + try { + // Check if user already exists + const existingUser = await prisma.user.findUnique({ + where: { clerkId: id }, + }); + + if (existingUser) { + console.log(`⚠️ User already exists: ${id}`); + return new Response("User already exists", { status: 200 }); + } + + // Create user in database + const user = await prisma.user.create({ + data: { + clerkId: id, + email: email_addresses[0]?.email_address ?? "", + firstName: first_name ?? null, + lastName: last_name ?? null, + imageUrl: image_url ?? null, + }, + }); + + console.log(`βœ… User created: ${user.email} (${user.id})`); + + return new Response("User created successfully", { status: 201 }); + } catch (error) { + console.error("❌ Error creating user:", error); + return new Response("Error creating user", { status: 500 }); + } + } + + // ═══════════════════════════════════════════════════ + // USER UPDATED + // ═══════════════════════════════════════════════════ + case "user.updated": { + const { id, email_addresses, first_name, last_name, image_url } = + evt.data; + + try { + const user = await prisma.user.update({ + where: { clerkId: id }, + data: { + email: email_addresses[0]?.email_address ?? "", + firstName: first_name ?? null, + lastName: last_name ?? null, + imageUrl: image_url ?? null, + }, + }); + + console.log(`βœ… User updated: ${user.email} (${user.id})`); + + return new Response("User updated successfully", { status: 200 }); + } catch (error) { + console.error("❌ Error updating user:", error); + return new Response("Error updating user", { status: 500 }); + } + } + + // ═══════════════════════════════════════════════════ + // USER DELETED + // ═══════════════════════════════════════════════════ + case "user.deleted": { + const { id } = evt.data; + + if (!id) { + console.error("❌ No user ID provided in deletion event"); + return new Response("No user ID provided", { status: 400 }); + } + + try { + // Delete user (CASCADE will delete all related contracts) + await prisma.user.delete({ + where: { clerkId: id }, + }); + + console.log(`βœ… User deleted: ${id}`); + + return new Response("User deleted successfully", { status: 200 }); + } catch (error) { + console.error("❌ Error deleting user:", error); + return new Response("Error deleting user", { status: 500 }); + } + } + + // ═══════════════════════════════════════════════════ + // OTHER EVENTS (ignore) + // ═══════════════════════════════════════════════════ + default: { + console.log(`ℹ️ Unhandled webhook event: ${eventType}`); + return new Response("Event type not handled", { status: 200 }); + } + } +} diff --git a/app/clerk-provider.tsx b/app/clerk-provider.tsx new file mode 100644 index 0000000..b4a28d5 --- /dev/null +++ b/app/clerk-provider.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { ClerkProvider } from "@clerk/nextjs"; +import { dark } from "@clerk/themes"; +import { useTheme } from "next-themes"; +import { ReactNode } from "react"; + +export function ClerkThemeProvider({ children }: { children: ReactNode }) { + const { resolvedTheme } = useTheme(); + const isDark = resolvedTheme === "dark"; + + return ( + + {children} + + ); +} diff --git a/app/globals.css b/app/globals.css index b2c5819..faecefc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -91,6 +91,38 @@ "rlig" 1, "calt" 1; } + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: + "rlig" 1, + "calt" 1; + } + + /* Smooth scrolling */ + html { + scroll-behavior: smooth; + } + + /* Better font rendering */ + body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + } + + /* Selection styles */ + ::selection { + @apply bg-primary/20; + } + + /* Focus styles */ + :focus-visible { + @apply outline-none ring-2 ring-primary ring-offset-2; + } } @layer utilities { diff --git a/app/layout.tsx b/app/layout.tsx index a91ec1e..5d610a2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,24 +1,89 @@ import type { Metadata } from "next"; -import { Poppins, Geist_Mono } from "next/font/google"; +import { Inter, JetBrains_Mono } from "next/font/google"; import "./globals.css"; import { Providers } from "./provider"; -const poppins = Poppins({ +// Modern sans-serif font for body text +const inter = Inter({ subsets: ["latin"], - weight: ["400", "500", "600", "700", "800", "900"], - variable: "--font-poppins", + variable: "--font-inter", display: "swap", + weight: ["300", "400", "500", "600", "700"], }); -const geistMono = Geist_Mono({ +// Monospace font for code and numbers +const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], - variable: "--font-geist-mono", + variable: "--font-mono", display: "swap", + weight: ["400", "500", "600", "700"], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: { + default: "LexiChain - AI-Powered Contract Management", + template: "%s | LexiChain", + }, + description: + "Intelligent BFSI contract management platform with AI-powered analysis. Manage your insurance, loan, and financial contracts with blockchain-verified security.", + keywords: [ + "contract management", + "insurance", + "BFSI", + "AI contract analysis", + "document management", + "blockchain", + "smart contracts", + ], + authors: [{ name: "Your Name" }], + creator: "Your Name", + publisher: "LexiChain", + metadataBase: new URL("https://lexichain.com"), // Replace with your domain + openGraph: { + type: "website", + locale: "fr_FR", + url: "https://lexichain.com", + title: "LexiChain - AI-Powered Contract Management", + description: + "Intelligent contract management platform with AI analysis and blockchain verification.", + siteName: "LexiChain", + images: [ + { + url: "/og-image.png", // Create this image (1200x630px) + width: 1200, + height: 630, + alt: "LexiChain Platform", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "LexiChain - AI-Powered Contract Management", + description: + "Intelligent contract management platform with AI analysis and blockchain verification.", + images: ["/og-image.png"], + creator: "@lexichain", // Replace with your Twitter handle + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, + icons: { + icon: [ + { url: "/favicon.ico" }, + { url: "/icon-192.png", sizes: "192x192", type: "image/png" }, + { url: "/icon-512.png", sizes: "512x512", type: "image/png" }, + ], + apple: [{ url: "/apple-icon.png", sizes: "180x180", type: "image/png" }], + }, + manifest: "/manifest.json", }; export default function RootLayout({ @@ -27,9 +92,18 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + + + {/* Preconnect to external domains for performance */} + + + {children} diff --git a/app/provider.tsx b/app/provider.tsx index 0da5e24..8ccf1d3 100644 --- a/app/provider.tsx +++ b/app/provider.tsx @@ -1,6 +1,9 @@ +// src/components/providers.tsx "use client"; + import { ThemeProvider } from "next-themes"; import { ReactNode } from "react"; +import { ClerkThemeProvider } from "./clerk-provider"; export function Providers({ children }: { children: ReactNode }) { return ( @@ -10,7 +13,7 @@ export function Providers({ children }: { children: ReactNode }) { enableSystem disableTransitionOnChange > - {children} + {children} ); } diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..07f1cd9c80b2d11e9ca7f71c499d9ed883c38449 GIT binary patch literal 5574 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 hf9q5K*U*nn1d&;awF>3W6aJQI^|g&)6yrQg literal 0 HcmV?d00001 diff --git a/components/ui/resizable.tsx b/components/ui/resizable.tsx index f4bc558..757bae8 100644 --- a/components/ui/resizable.tsx +++ b/components/ui/resizable.tsx @@ -1,36 +1,36 @@ -"use client" +"use client"; -import { GripVertical } from "lucide-react" -import * as ResizablePrimitive from "react-resizable-panels" +import { GripVertical } from "lucide-react"; +import * as ResizablePrimitive from "react-resizable-panels"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const ResizablePanelGroup = ({ className, ...props -}: React.ComponentProps) => ( - ) => ( + -) +); -const ResizablePanel = ResizablePrimitive.Panel +const ResizablePanel = ResizablePrimitive.Panel; const ResizableHandle = ({ withHandle, className, ...props -}: React.ComponentProps & { - withHandle?: boolean +}: React.ComponentProps & { + withHandle?: boolean; }) => ( - div]:rotate-90", - className + className, )} {...props} > @@ -39,7 +39,7 @@ const ResizableHandle = ({
)} - -) + +); -export { ResizablePanelGroup, ResizablePanel, ResizableHandle } +export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; diff --git a/components/views/Home/Hero.tsx b/components/views/Home/Hero.tsx index 613fb87..0853e90 100644 --- a/components/views/Home/Hero.tsx +++ b/components/views/Home/Hero.tsx @@ -8,13 +8,15 @@ import { Lock, Check, Zap, - Link, + Link2, FileText, MessageSquare, Shield, TrendingUp, Bell, } from "lucide-react"; +import Link from "next/link"; +import { SignedIn, SignedOut } from "@clerk/nextjs"; // Ripple Effect Component function BackgroundRipple() { @@ -407,7 +409,7 @@ export function Hero() { { icon: Lock, label: "Bank-Level Security" }, { icon: Check, label: "GDPR Certified" }, { icon: Zap, label: "Real-Time AI" }, - { icon: Link, label: "Blockchain Verified" }, + { icon: Link2, label: "Blockchain Verified" }, ]; return ( @@ -544,8 +546,10 @@ export function Hero() { }`} > {/* Primary CTA */} - + /> + + + + + + + +
{/* Trust Indicators */} diff --git a/components/views/Home/HowItWorks.tsx b/components/views/Home/HowItWorks.tsx index 30557fb..7c97c67 100644 --- a/components/views/Home/HowItWorks.tsx +++ b/components/views/Home/HowItWorks.tsx @@ -440,7 +440,7 @@ export function HowItWorks() { number: "02", title: "AI Analysis", description: - "GPT-4 Turbo extracts and analyzes every clause, term, and detail automatically.", + "AI extracts and analyzes every clause, term, and detail automatically.", icon: Cpu, gradient: "bg-gradient-to-br from-violet-500 to-purple-600", glowColor: "rgba(139, 92, 246, 0.5)", diff --git a/components/views/Home/Navbar.tsx b/components/views/Home/Navbar.tsx index 935bc28..605ebaf 100644 --- a/components/views/Home/Navbar.tsx +++ b/components/views/Home/Navbar.tsx @@ -3,8 +3,16 @@ import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { ModeToggle } from "@/components/ui/mode-toggle"; -import { Sparkles, X, ArrowRight } from "lucide-react"; +import { X, ArrowRight } from "lucide-react"; import Image from "next/image"; +import Link from "next/link"; +import { + SignedIn, + SignedOut, + SignInButton, + SignUpButton, + UserButton, +} from "@clerk/nextjs"; const navLinks = [ { label: "Features", href: "#features" }, @@ -17,12 +25,6 @@ export function Navbar() { const [isScrolled, setIsScrolled] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [activeLink, setActiveLink] = useState(""); - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - useEffect(() => { const handleScroll = () => { setIsScrolled(window.scrollY > 20); @@ -46,26 +48,6 @@ export function Navbar() { setIsMobileMenuOpen(false); } }; - - if (!mounted) { - return ( - - ); - } - return ( <>