From 0c8593ef2264e51f41aa4e6a7711cec785a44c8e Mon Sep 17 00:00:00 2001 From: CycroftX Date: Tue, 10 Feb 2026 11:35:23 +0530 Subject: [PATCH] feat: Add Multi-Gateway Configuration Module with Payment Settings Tab --- MASTER_API_INVENTORY.md | 270 ++++++++++++++++++ .../components/GatewayConfigSheet.tsx | 219 ++++++++++++++ .../settings/components/SettingsLayout.tsx | 143 ++++++++++ .../components/tabs/OrganizationSettings.tsx | 158 ++++++++++ .../components/tabs/PartnerGovernance.tsx | 173 +++++++++++ .../components/tabs/PaymentConfig.tsx | 196 +++++++++++++ .../components/tabs/PublicAppConfig.tsx | 193 +++++++++++++ .../settings/components/tabs/SystemHealth.tsx | 244 ++++++++++++++++ src/lib/actions/payment-settings.ts | 79 +++++ src/lib/actions/settings.ts | 95 ++++++ src/lib/payment-encryption.ts | 33 +++ src/lib/types/settings.ts | 173 +++++++++++ src/pages/Settings.tsx | 28 +- 13 files changed, 1979 insertions(+), 25 deletions(-) create mode 100644 MASTER_API_INVENTORY.md create mode 100644 src/features/settings/components/GatewayConfigSheet.tsx create mode 100644 src/features/settings/components/SettingsLayout.tsx create mode 100644 src/features/settings/components/tabs/OrganizationSettings.tsx create mode 100644 src/features/settings/components/tabs/PartnerGovernance.tsx create mode 100644 src/features/settings/components/tabs/PaymentConfig.tsx create mode 100644 src/features/settings/components/tabs/PublicAppConfig.tsx create mode 100644 src/features/settings/components/tabs/SystemHealth.tsx create mode 100644 src/lib/actions/payment-settings.ts create mode 100644 src/lib/actions/settings.ts create mode 100644 src/lib/payment-encryption.ts create mode 100644 src/lib/types/settings.ts diff --git a/MASTER_API_INVENTORY.md b/MASTER_API_INVENTORY.md new file mode 100644 index 0000000..3a37fbc --- /dev/null +++ b/MASTER_API_INVENTORY.md @@ -0,0 +1,270 @@ +# Eventify Master API Inventory + +> **Version:** 1.0 | **Last Updated:** 2026-02-09 | **Status:** Definitive Reference + +--- + +## 1. Auth & Identity + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| AUTH-01 | `/api/v1/auth/login/otp/request` | POST | Inbound | App/Web -> Server | Request OTP for phone login | High | +| AUTH-02 | `/api/v1/auth/login/otp/verify` | POST | Inbound | App/Web -> Server | Verify OTP and issue tokens | High | +| AUTH-03 | `/api/v1/auth/login/password` | POST | Inbound | App/Web -> Server | Email/password login | High | +| AUTH-04 | `/api/v1/auth/register` | POST | Inbound | App/Web -> Server | New user registration | High | +| AUTH-05 | `/api/v1/auth/logout` | POST | Inbound | App/Web -> Server | Invalidate session/tokens | Medium | +| AUTH-06 | `/api/v1/auth/refresh` | POST | Inbound | App/Web -> Server | Refresh access token | High | +| AUTH-07 | `/api/v1/auth/forgot-password` | POST | Inbound | App/Web -> Server | Initiate password reset | Medium | +| AUTH-08 | `/api/v1/auth/reset-password` | POST | Inbound | App/Web -> Server | Complete password reset | Medium | +| AUTH-09 | `/api/v1/auth/mfa/setup` | POST | Inbound | App/Web -> Server | Enable 2FA (TOTP/SMS) | Medium | +| AUTH-10 | `/api/v1/auth/mfa/verify` | POST | Inbound | App/Web -> Server | Verify 2FA code | High | +| AUTH-11 | `/api/v1/auth/oauth/google` | GET | Inbound | App/Web -> Server | Google OAuth redirect | Medium | +| AUTH-12 | `/api/v1/auth/oauth/google/callback` | GET | Inbound | Google -> Server | Google OAuth callback | Medium | +| AUTH-13 | `/api/v1/auth/oauth/apple` | GET | Inbound | App -> Server | Apple Sign-In redirect | Medium | +| AUTH-14 | `/api/v1/auth/oauth/apple/callback` | POST | Inbound | Apple -> Server | Apple Sign-In callback | Medium | +| AUTH-15 | `/api/v1/auth/sessions` | GET | Inbound | App/Web -> Server | List active sessions | Low | +| AUTH-16 | `/api/v1/auth/sessions/:id` | DELETE | Inbound | App/Web -> Server | Revoke specific session | Medium | +| AUTH-17 | `/api/v1/auth/sessions/all` | DELETE | Inbound | App/Web -> Server | Revoke all sessions (except current) | Medium | +| AUTH-18 | `/api/v1/partner/auth/login` | POST | Inbound | Partner -> Server | Partner/Organizer login | High | +| AUTH-19 | `/api/v1/admin/auth/login` | POST | Inbound | Control Center -> Server | Admin login | High | +| AUTH-20 | `/api/v1/admin/auth/impersonate/:userId` | POST | Inbound | Control Center -> Server | Impersonate user session | High | + +--- + +## 2. User Entity (CRUD & Profile) + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| USER-01 | `/api/v1/users/me` | GET | Inbound | App/Web -> Server | Get current user profile | High | +| USER-02 | `/api/v1/users/me` | PATCH | Inbound | App/Web -> Server | Update profile (name, bio, avatar) | Medium | +| USER-03 | `/api/v1/users/me/phone` | PATCH | Inbound | App/Web -> Server | Update phone number | Medium | +| USER-04 | `/api/v1/users/me/email` | PATCH | Inbound | App/Web -> Server | Update email address | Medium | +| USER-05 | `/api/v1/users/me/preferences` | PATCH | Inbound | App/Web -> Server | Update notification/privacy preferences | Low | +| USER-06 | `/api/v1/users/me/avatar` | POST | Inbound | App/Web -> Server | Upload avatar image | Low | +| USER-07 | `/api/v1/users/me/delete` | POST | Inbound | App/Web -> Server | Request account deletion (GDPR) | Medium | +| USER-08 | `/api/v1/users/:id` | GET | Inbound | App/Web -> Server | Get public user profile | Low | +| USER-09 | `/api/v1/users/:id/follow` | POST | Inbound | App -> Server | Follow another user | Low | +| USER-10 | `/api/v1/users/:id/unfollow` | POST | Inbound | App -> Server | Unfollow user | Low | +| USER-11 | `/api/v1/users/me/followers` | GET | Inbound | App -> Server | List followers | Low | +| USER-12 | `/api/v1/users/me/following` | GET | Inbound | App -> Server | List following | Low | +| USER-13 | `/api/v1/admin/users` | GET | Inbound | Control Center -> Server | List all users (paginated, filterable) | High | +| USER-14 | `/api/v1/admin/users/:id` | GET | Inbound | Control Center -> Server | Get user details (admin view) | High | +| USER-15 | `/api/v1/admin/users/:id` | PATCH | Inbound | Control Center -> Server | Update user (admin override) | High | +| USER-16 | `/api/v1/admin/users/:id/suspend` | POST | Inbound | Control Center -> Server | Suspend user account | High | +| USER-17 | `/api/v1/admin/users/:id/ban` | POST | Inbound | Control Center -> Server | Ban user permanently | High | +| USER-18 | `/api/v1/admin/users/:id/reinstate` | POST | Inbound | Control Center -> Server | Reinstate suspended/banned user | High | +| USER-19 | `/api/v1/admin/users/:id/notes` | POST | Inbound | Control Center -> Server | Add internal admin note | Low | +| USER-20 | `/api/v1/admin/users/:id/tags` | PATCH | Inbound | Control Center -> Server | Update user tags | Low | + +--- + +## 3. Events (Discovery, CRUD, Publishing) + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| EVT-01 | `/api/v1/events` | GET | Inbound | App/Web -> Server | List/search events (public) | High | +| EVT-02 | `/api/v1/events/:id` | GET | Inbound | App/Web -> Server | Get event details | High | +| EVT-03 | `/api/v1/events/:id/tickets` | GET | Inbound | App/Web -> Server | Get ticket types for event | High | +| EVT-04 | `/api/v1/events/featured` | GET | Inbound | App/Web -> Server | Get featured/promoted events | Medium | +| EVT-05 | `/api/v1/events/nearby` | GET | Inbound | App -> Server | Get events near user (geo) | Medium | +| EVT-06 | `/api/v1/events/categories` | GET | Inbound | App/Web -> Server | List event categories | Low | +| EVT-07 | `/api/v1/events/:id/similar` | GET | Inbound | App/Web -> Server | Get similar events | Low | +| EVT-08 | `/api/v1/partner/events` | GET | Inbound | Partner -> Server | List organizer's events | High | +| EVT-09 | `/api/v1/partner/events` | POST | Inbound | Partner -> Server | Create new event | High | +| EVT-10 | `/api/v1/partner/events/:id` | GET | Inbound | Partner -> Server | Get event details (owner) | High | +| EVT-11 | `/api/v1/partner/events/:id` | PATCH | Inbound | Partner -> Server | Update event | High | +| EVT-12 | `/api/v1/partner/events/:id` | DELETE | Inbound | Partner -> Server | Delete/cancel event | High | +| EVT-13 | `/api/v1/partner/events/:id/publish` | POST | Inbound | Partner -> Server | Publish event (make live) | High | +| EVT-14 | `/api/v1/partner/events/:id/unpublish` | POST | Inbound | Partner -> Server | Unpublish/draft event | Medium | +| EVT-15 | `/api/v1/partner/events/:id/duplicate` | POST | Inbound | Partner -> Server | Clone event | Low | +| EVT-16 | `/api/v1/partner/events/:id/tickets` | POST | Inbound | Partner -> Server | Create ticket type | High | +| EVT-17 | `/api/v1/partner/events/:id/tickets/:ticketId` | PATCH | Inbound | Partner -> Server | Update ticket type | High | +| EVT-18 | `/api/v1/partner/events/:id/tickets/:ticketId` | DELETE | Inbound | Partner -> Server | Delete ticket type | Medium | +| EVT-19 | `/api/v1/admin/events` | GET | Inbound | Control Center -> Server | List all events (admin) | High | +| EVT-20 | `/api/v1/admin/events/:id` | PATCH | Inbound | Control Center -> Server | Override event details | High | +| EVT-21 | `/api/v1/admin/events/:id/approve` | POST | Inbound | Control Center -> Server | Approve pending event | High | +| EVT-22 | `/api/v1/admin/events/:id/reject` | POST | Inbound | Control Center -> Server | Reject pending event | High | +| EVT-23 | `/api/v1/admin/events/:id/feature` | POST | Inbound | Control Center -> Server | Feature event on homepage | Medium | + +--- + +## 4. Orders & Tickets (Commerce) + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| ORD-01 | `/api/v1/orders` | POST | Inbound | App/Web -> Server | Create order (cart checkout) | High | +| ORD-02 | `/api/v1/orders/:id` | GET | Inbound | App/Web -> Server | Get order details | High | +| ORD-03 | `/api/v1/orders/:id/pay` | POST | Inbound | App/Web -> Server | Initiate payment for order | High | +| ORD-04 | `/api/v1/orders/:id/cancel` | POST | Inbound | App/Web -> Server | Cancel pending order | Medium | +| ORD-05 | `/api/v1/users/me/orders` | GET | Inbound | App/Web -> Server | List user's orders | High | +| ORD-06 | `/api/v1/users/me/tickets` | GET | Inbound | App/Web -> Server | List user's tickets (wallet) | High | +| ORD-07 | `/api/v1/tickets/:id` | GET | Inbound | App/Web -> Server | Get ticket details (for wallet) | High | +| ORD-08 | `/api/v1/tickets/:id/qr` | GET | Inbound | App -> Server | Get QR code data for ticket | High | +| ORD-09 | `/api/v1/tickets/:id/transfer` | POST | Inbound | App -> Server | Transfer ticket to another user | Medium | +| ORD-10 | `/api/v1/tickets/:id/accept-transfer` | POST | Inbound | App -> Server | Accept incoming ticket transfer | Medium | +| ORD-11 | `/api/v1/partner/orders` | GET | Inbound | Partner -> Server | List orders for organizer's events | High | +| ORD-12 | `/api/v1/partner/orders/:id` | GET | Inbound | Partner -> Server | Get order details (organizer) | High | +| ORD-13 | `/api/v1/partner/orders/:id/refund` | POST | Inbound | Partner -> Server | Initiate refund | High | +| ORD-14 | `/api/v1/partner/attendees` | GET | Inbound | Partner -> Server | List attendees for event | High | +| ORD-15 | `/api/v1/admin/orders` | GET | Inbound | Control Center -> Server | List all orders (admin) | High | +| ORD-16 | `/api/v1/admin/orders/:id` | GET | Inbound | Control Center -> Server | Get order details (admin) | High | +| ORD-17 | `/api/v1/admin/orders/:id/refund` | POST | Inbound | Control Center -> Server | Force refund (admin override) | High | +| ORD-18 | `/api/v1/admin/orders/:id/receipt` | GET | Inbound | Control Center -> Server | Generate/download receipt | Medium | + +--- + +## 5. Payments & Payouts + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| PAY-01 | `/api/v1/payments/razorpay/create-order` | POST | Inbound | App/Web -> Server | Create Razorpay order | High | +| PAY-02 | `/api/v1/payments/razorpay/verify` | POST | Inbound | App/Web -> Server | Verify Razorpay payment signature | High | +| PAY-03 | `/api/v1/payments/stripe/create-intent` | POST | Inbound | App/Web -> Server | Create Stripe PaymentIntent | High | +| PAY-04 | `/api/v1/payments/stripe/confirm` | POST | Inbound | App/Web -> Server | Confirm Stripe payment | High | +| PAY-05 | `/api/v1/webhooks/razorpay` | POST | Inbound | Razorpay -> Server | Razorpay webhook events | High | +| PAY-06 | `/api/v1/webhooks/stripe` | POST | Inbound | Stripe -> Server | Stripe webhook events | High | +| PAY-07 | `/api/v1/partner/payouts` | GET | Inbound | Partner -> Server | List payout history | High | +| PAY-08 | `/api/v1/partner/payouts/:id` | GET | Inbound | Partner -> Server | Get payout details | High | +| PAY-09 | `/api/v1/partner/bank-account` | GET | Inbound | Partner -> Server | Get linked bank account | High | +| PAY-10 | `/api/v1/partner/bank-account` | POST | Inbound | Partner -> Server | Link bank account for payouts | High | +| PAY-11 | `/api/v1/partner/bank-account` | PATCH | Inbound | Partner -> Server | Update bank account | High | +| PAY-12 | `/api/v1/admin/payouts` | GET | Inbound | Control Center -> Server | List all payouts (admin) | High | +| PAY-13 | `/api/v1/admin/payouts/:id/approve` | POST | Inbound | Control Center -> Server | Approve payout | High | +| PAY-14 | `/api/v1/admin/payouts/:id/hold` | POST | Inbound | Control Center -> Server | Hold payout | High | +| PAY-15 | `/api/v1/admin/payouts/:id/release` | POST | Inbound | Control Center -> Server | Release held payout | High | +| PAY-16 | `Razorpay Payout API` | POST | Outbound | Server -> Razorpay | Initiate payout to organizer | High | +| PAY-17 | `Stripe Connect Payout` | POST | Outbound | Server -> Stripe | Transfer funds to connected account | High | + +--- + +## 6. Operations (Check-in, Scanning, Staff) + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| OPS-01 | `/api/v1/partner/scan/validate` | POST | Inbound | Partner App -> Server | Validate scanned QR code | High | +| OPS-02 | `/api/v1/partner/scan/checkin` | POST | Inbound | Partner App -> Server | Check-in attendee | High | +| OPS-03 | `/api/v1/partner/scan/checkout` | POST | Inbound | Partner App -> Server | Check-out attendee (optional) | Low | +| OPS-04 | `/api/v1/partner/events/:id/checkin-stats` | GET | Inbound | Partner -> Server | Get live check-in statistics | Medium | +| OPS-05 | `/api/v1/partner/staff` | GET | Inbound | Partner -> Server | List event staff | Medium | +| OPS-06 | `/api/v1/partner/staff` | POST | Inbound | Partner -> Server | Invite staff member | Medium | +| OPS-07 | `/api/v1/partner/staff/:id` | DELETE | Inbound | Partner -> Server | Remove staff member | Medium | +| OPS-08 | `/api/v1/partner/staff/:id/permissions` | PATCH | Inbound | Partner -> Server | Update staff permissions | Medium | +| OPS-09 | `/api/v1/staff/auth/login` | POST | Inbound | Staff App -> Server | Staff member login | High | +| OPS-10 | `/api/v1/staff/events` | GET | Inbound | Staff App -> Server | List assigned events | Medium | + +--- + +## 7. Communication (Email, Push, SMS, Chat) + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| COM-01 | `Resend API - Transactional` | POST | Outbound | Server -> Resend | Send order confirmation email | High | +| COM-02 | `Resend API - Marketing` | POST | Outbound | Server -> Resend | Send marketing/promo email | Low | +| COM-03 | `FCM - Push Notification` | POST | Outbound | Server -> FCM | Send push notification to user | Medium | +| COM-04 | `APNs - Push Notification` | POST | Outbound | Server -> APNs | Send iOS push notification | Medium | +| COM-05 | `Twilio SMS` | POST | Outbound | Server -> Twilio | Send SMS (OTP, alerts) | High | +| COM-06 | `/api/v1/users/me/devices` | POST | Inbound | App -> Server | Register device for push | Medium | +| COM-07 | `/api/v1/users/me/devices/:id` | DELETE | Inbound | App -> Server | Unregister device | Low | +| COM-08 | `/api/v1/webhooks/resend` | POST | Inbound | Resend -> Server | Email delivery status webhook | Medium | +| COM-09 | `/api/v1/webhooks/twilio` | POST | Inbound | Twilio -> Server | SMS delivery status webhook | Medium | +| COM-10 | `/api/v1/admin/notifications/send` | POST | Inbound | Control Center -> Server | Send notification to user | Medium | +| COM-11 | `/api/v1/admin/notifications/broadcast` | POST | Inbound | Control Center -> Server | Broadcast to user segment | Medium | +| COM-12 | `/api/v1/chat/conversations` | GET | Inbound | App -> Server | List user's conversations | Low | +| COM-13 | `/api/v1/chat/conversations/:id/messages` | GET | Inbound | App -> Server | Get messages in conversation | Low | +| COM-14 | `/api/v1/chat/conversations/:id/messages` | POST | Inbound | App -> Server | Send message | Low | +| COM-15 | `WebSocket /ws/chat` | WS | Bidirectional | App <-> Server | Real-time chat connection | Low | + +--- + +## 8. Data & Analytics + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| DAT-01 | `/api/v1/partner/analytics/overview` | GET | Inbound | Partner -> Server | Get sales overview dashboard | Medium | +| DAT-02 | `/api/v1/partner/analytics/sales` | GET | Inbound | Partner -> Server | Get detailed sales data | Medium | +| DAT-03 | `/api/v1/partner/analytics/attendees` | GET | Inbound | Partner -> Server | Get attendee demographics | Low | +| DAT-04 | `/api/v1/partner/analytics/traffic` | GET | Inbound | Partner -> Server | Get page view/traffic data | Low | +| DAT-05 | `/api/v1/partner/export/attendees` | GET | Outbound | Server -> Browser | Export attendee list (CSV) | Medium | +| DAT-06 | `/api/v1/partner/export/orders` | GET | Outbound | Server -> Browser | Export orders (CSV) | Medium | +| DAT-07 | `/api/v1/admin/analytics/overview` | GET | Inbound | Control Center -> Server | Platform-wide analytics | Medium | +| DAT-08 | `/api/v1/admin/analytics/revenue` | GET | Inbound | Control Center -> Server | Revenue breakdown | High | +| DAT-09 | `/api/v1/admin/analytics/users` | GET | Inbound | Control Center -> Server | User growth metrics | Medium | +| DAT-10 | `/api/v1/admin/export/users` | GET | Outbound | Server -> Browser | Export users (CSV) | Medium | +| DAT-11 | `/api/v1/admin/export/orders` | GET | Outbound | Server -> Browser | Export orders (CSV) | Medium | +| DAT-12 | `/api/v1/admin/export/payouts` | GET | Outbound | Server -> Browser | Export payouts (CSV) | Medium | +| DAT-13 | `/api/v1/admin/audit-logs` | GET | Inbound | Control Center -> Server | Get admin activity logs | High | + +--- + +## 9. Third-Party Integrations + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| INT-01 | `AWS S3 - Presigned URL` | POST | Outbound | Server -> S3 | Generate upload URL | Medium | +| INT-02 | `/api/v1/uploads/presigned` | GET | Inbound | App/Web -> Server | Request presigned URL for upload | Medium | +| INT-03 | `Google Maps Geocoding API` | GET | Outbound | Server -> Google | Geocode event address | Low | +| INT-04 | `Google Maps Places API` | GET | Outbound | Server -> Google | Autocomplete venue search | Low | +| INT-05 | `/api/v1/geo/ip` | GET | Inbound | App/Web -> Server | Get location from IP | Low | +| INT-06 | `MaxMind GeoIP` | GET | Internal | Server -> MaxMind DB | IP to location lookup | Low | +| INT-07 | `/api/v1/calendar/google/export` | GET | Inbound | App/Web -> Server | Generate Google Calendar link | Low | +| INT-08 | `/api/v1/calendar/ical/export` | GET | Inbound | App/Web -> Server | Generate iCal file | Low | +| INT-09 | `Sentry - Error Reporting` | POST | Outbound | Server -> Sentry | Log errors | Medium | +| INT-10 | `Slack Webhook` | POST | Outbound | Server -> Slack | Alert on critical events | Medium | +| INT-11 | `PagerDuty API` | POST | Outbound | Server -> PagerDuty | Trigger on-call alerts | High | + +--- + +## 10. Support & Moderation + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| SUP-01 | `/api/v1/admin/tickets` | GET | Inbound | Control Center -> Server | List support tickets | Medium | +| SUP-02 | `/api/v1/admin/tickets/:id` | GET | Inbound | Control Center -> Server | Get ticket details | Medium | +| SUP-03 | `/api/v1/admin/tickets` | POST | Inbound | Control Center -> Server | Create escalation ticket | Medium | +| SUP-04 | `/api/v1/admin/tickets/:id/reply` | POST | Inbound | Control Center -> Server | Reply to ticket | Medium | +| SUP-05 | `/api/v1/admin/tickets/:id/close` | POST | Inbound | Control Center -> Server | Close ticket | Medium | +| SUP-06 | `/api/v1/admin/tickets/:id/assign` | POST | Inbound | Control Center -> Server | Assign ticket to agent | Medium | +| SUP-07 | `/api/v1/users/me/tickets` | GET | Inbound | App/Web -> Server | List user's support tickets | Low | +| SUP-08 | `/api/v1/users/me/tickets` | POST | Inbound | App/Web -> Server | Create support ticket | Low | +| SUP-09 | `/api/v1/admin/moderation/reports` | GET | Inbound | Control Center -> Server | List content reports | Medium | +| SUP-10 | `/api/v1/admin/moderation/reports/:id/action` | POST | Inbound | Control Center -> Server | Take action on report | Medium | + +--- + +## 11. System & Utilities + +| ID | Endpoint / Action Name | Method | Direction | Source -> Target | Purpose | Criticality | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| SYS-01 | `/api/v1/health` | GET | Inbound | Any -> Server | Health check | High | +| SYS-02 | `/api/v1/health/db` | GET | Inbound | Internal -> Server | Database connectivity check | High | +| SYS-03 | `/api/v1/health/redis` | GET | Inbound | Internal -> Server | Redis connectivity check | High | +| SYS-04 | `/api/v1/config/mobile` | GET | Inbound | App -> Server | Get mobile app config (feature flags) | Medium | +| SYS-05 | `/api/v1/config/web` | GET | Inbound | Web -> Server | Get web config (feature flags) | Medium | +| SYS-06 | `/api/v1/version` | GET | Inbound | Any -> Server | Get API version info | Low | +| SYS-07 | `Cron: Payout Processor` | CRON | Internal | Scheduler -> Server | Process pending payouts | High | +| SYS-08 | `Cron: Reminder Emails` | CRON | Internal | Scheduler -> Server | Send event reminder emails | Medium | +| SYS-09 | `Cron: Expired Orders Cleanup` | CRON | Internal | Scheduler -> Server | Cancel expired pending orders | Medium | +| SYS-10 | `Cron: Analytics Aggregation` | CRON | Internal | Scheduler -> Server | Pre-compute analytics | Low | + +--- + +## Summary Statistics + +| Category | Total Endpoints | +| :--- | :---: | +| Auth & Identity | 20 | +| User Entity | 20 | +| Events | 23 | +| Orders & Tickets | 18 | +| Payments & Payouts | 17 | +| Operations | 10 | +| Communication | 15 | +| Data & Analytics | 13 | +| Third-Party Integrations | 11 | +| Support & Moderation | 10 | +| System & Utilities | 10 | +| **TOTAL** | **167** | + +--- + +> **Note:** This inventory represents the definitive API surface for the Eventify ecosystem. All development should reference this document for endpoint naming, method conventions, and integration points. diff --git a/src/features/settings/components/GatewayConfigSheet.tsx b/src/features/settings/components/GatewayConfigSheet.tsx new file mode 100644 index 0000000..0361955 --- /dev/null +++ b/src/features/settings/components/GatewayConfigSheet.tsx @@ -0,0 +1,219 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { GatewayProvider, GatewayCredentials } from '@/lib/types/settings'; +import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Switch } from '@/components/ui/switch'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { verifyGatewayCredentials, saveGatewayConfig } from '@/lib/actions/payment-settings'; +import { toast } from 'sonner'; +import { Lock, CheckBase, Loader2, Copy } from 'lucide-react'; +import { decrypt } from '@/lib/payment-encryption'; + +interface GatewayConfigSheetProps { + open: boolean; + onOpenChange: (open: boolean) => void; + provider: GatewayProvider; + initialConfig: GatewayCredentials; + onSave: () => void; +} + +export function GatewayConfigSheet({ open, onOpenChange, provider, initialConfig, onSave }: GatewayConfigSheetProps) { + const [config, setConfig] = useState(initialConfig); + const [loading, setLoading] = useState(false); + const [verifying, setVerifying] = useState(false); + + // Reset config when provider changes + useEffect(() => { + // Decrypt sensitive fields for editing + const decrypted = { ...initialConfig }; + if (decrypted.salt) decrypted.salt = decrypt(decrypted.salt); + setConfig(decrypted); + }, [provider, initialConfig, open]); + + const handleVerify = async () => { + setVerifying(true); + try { + const res = await verifyGatewayCredentials(provider, config); + if (res.success) { + toast.success('Credentials Verified', { description: res.message }); + } else { + toast.error('Verification Failed', { description: res.message }); + } + } catch (error) { + toast.error('Verification Error'); + } finally { + setVerifying(false); + } + }; + + const handleSave = async () => { + setLoading(true); + try { + const res = await saveGatewayConfig(provider, config); + if (res.success) { + toast.success(`Saved ${provider} configuration`); + onSave(); + onOpenChange(false); + } else { + toast.error(res.message); + } + } catch (error) { + toast.error('Failed to save configuration'); + } finally { + setLoading(false); + } + }; + + return ( + + + + + {provider} Configuration + + + Configure API keys and secrets for {provider}. + + + +
+ {/* Environment Toggle */} +
+
+ +
+ {config.mode === 'live' ? 'Production / Live Traffic' : 'Sandbox / Test Mode'} +
+
+ setConfig({ ...config, mode: c ? 'live' : 'test' })} + /> +
+ + + + Credentials + Features + Webhooks + + + + {/* Dynamic inputs based on provider */} + {(provider === 'razorpay' || provider === 'easebuzz') && ( +
+ + setConfig({ ...config, keyId: e.target.value, merchantId: e.target.value })} + placeholder="rzp_test_..." + /> +
+ )} + + {(provider === 'stripe') && ( +
+ + setConfig({ ...config, publicKey: e.target.value })} + placeholder="pk_test_..." + /> +
+ )} + + {(provider === 'payu' || provider === 'easebuzz') && ( +
+ +
+ setConfig({ ...config, salt: e.target.value })} + placeholder="Enter secret salt" + className="pr-10" + /> + +
+
+ )} + + +
+ + +
+
+ setConfig({ ...config, features: { ...config.features, netbanking: c } })} + /> + +
+
+ setConfig({ ...config, features: { ...config.features, upi: c } })} + /> + +
+
+ setConfig({ ...config, features: { ...config.features, cards: c } })} + /> + +
+
+ setConfig({ ...config, features: { ...config.features, emi: c } })} + /> + +
+
+
+ + +
+ +
+ + +
+

Add this URL to your provider dashboard.

+
+
+ + setConfig({ ...config, webhookSecret: e.target.value })} + placeholder="whsec_..." + /> +
+
+
+
+ + + + +
+
+ ); +} diff --git a/src/features/settings/components/SettingsLayout.tsx b/src/features/settings/components/SettingsLayout.tsx new file mode 100644 index 0000000..02d8685 --- /dev/null +++ b/src/features/settings/components/SettingsLayout.tsx @@ -0,0 +1,143 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Card } from '@/components/ui/card'; +import { getSystemSettings } from '@/lib/actions/settings'; +import { GlobalSettings, DEFAULT_SETTINGS } from '@/lib/types/settings'; +import { OrganizationSettings } from './tabs/OrganizationSettings'; +import { PublicAppConfigTab } from './tabs/PublicAppConfig'; +import { PartnerGovernanceTab } from './tabs/PartnerGovernance'; +import { SystemHealthTab } from './tabs/SystemHealth'; +import { PaymentConfigTab } from './tabs/PaymentConfig'; +import { Loader2, Settings, Smartphone, Building2, Handshake, Server, CreditCard } from 'lucide-react'; + +export function SettingsLayout() { + const [settings, setSettings] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function loadSettings() { + try { + const res = await getSystemSettings(); + if (res.success) { + setSettings(res.data); + } else { + setSettings(DEFAULT_SETTINGS); // Fallback + } + } catch (err) { + console.error(err); + setSettings(DEFAULT_SETTINGS); + } finally { + setLoading(false); + } + } + loadSettings(); + }, []); + + const handleUpdate = (section: keyof GlobalSettings, data: any) => { + if (!settings) return; + setSettings({ + ...settings, + [section]: { ...settings[section], ...data } + }); + }; + + if (loading || !settings) { + return ( +
+ +

Loading system configuration...

+
+ ); + } + + return ( +
+
+
+

System Settings

+

+ Global configuration for User App, Partner Dashboard, and Backoffice. +

+
+
+ + + + + + + Organization + + + + Payment Gateways + + + + Public App + + + + Partner Governance + + + + System Health + + + + +
+ + + + + handleUpdate('payment', data)} + /> + + + handleUpdate('publicApp', data)} + /> + + + handleUpdate('partner', data)} + /> + + + handleUpdate('system', data)} + /> + +
+
+
+ ); +} diff --git a/src/features/settings/components/tabs/OrganizationSettings.tsx b/src/features/settings/components/tabs/OrganizationSettings.tsx new file mode 100644 index 0000000..542f07c --- /dev/null +++ b/src/features/settings/components/tabs/OrganizationSettings.tsx @@ -0,0 +1,158 @@ +'use client'; + +import { useState } from 'react'; +import { OrganizationConfig, SecurityConfig } from '@/lib/types/settings'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Switch } from '@/components/ui/switch'; +import { updateSystemSetting } from '@/lib/actions/settings'; +import { toast } from 'sonner'; +import { Building2, ShieldCheck, Mail, Globe } from 'lucide-react'; + +interface OrganizationSettingsProps { + orgConfig: OrganizationConfig; + securityConfig: SecurityConfig; + onUpdate: (section: 'organization' | 'security', data: any) => void; +} + +export function OrganizationSettings({ orgConfig, securityConfig, onUpdate }: OrganizationSettingsProps) { + const [loading, setLoading] = useState(false); + + // Internal state for form usage + const [orgState, setOrgState] = useState(orgConfig); + const [secState, setSecState] = useState(securityConfig); + + const handleSaveOrg = async () => { + setLoading(true); + try { + const res = await updateSystemSetting('organization', orgState); + if (res.success) { + toast.success('Organization profile updated'); + onUpdate('organization', res.updatedSettings.organization); + } + } catch (error) { + toast.error('Failed to update organization settings'); + } finally { + setLoading(false); + } + }; + + const handleSaveSecurity = async () => { + setLoading(true); + try { + const res = await updateSystemSetting('security', secState); + if (res.success) { + toast.success('Security policy updated'); + onUpdate('security', res.updatedSettings.security); + } + } catch (error) { + toast.error('Failed to update security settings'); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + + + Organization Profile + + + General branding and contact details for the control center and emails. + + + +
+
+ + setOrgState({ ...orgState, brandName: e.target.value })} + /> +
+
+ +
+ + setOrgState({ ...orgState, supportEmail: e.target.value })} + /> +
+
+
+
+ + setOrgState({ ...orgState, legalAddress: e.target.value })} + /> +
+ +
+ +
+
+
+ + + + + + Security Policy + + + Enforce security rules for all Control Center staff members. + + + +
+
+ +

+ Require Two-Factor Authentication for all admin accounts. +

+
+ setSecState({ ...secState, enforce2FA: checked })} + /> +
+ +
+
+ + setSecState({ ...secState, sessionTimeoutMinutes: parseInt(e.target.value) })} + /> +
+
+ + setSecState({ ...secState, passwordExpirationDays: parseInt(e.target.value) })} + /> +
+
+ +
+ +
+
+
+
+ ); +} diff --git a/src/features/settings/components/tabs/PartnerGovernance.tsx b/src/features/settings/components/tabs/PartnerGovernance.tsx new file mode 100644 index 0000000..b016ade --- /dev/null +++ b/src/features/settings/components/tabs/PartnerGovernance.tsx @@ -0,0 +1,173 @@ +'use client'; + +import { useState } from 'react'; +import { PartnerConfig } from '@/lib/types/settings'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Switch } from '@/components/ui/switch'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; +import { updateSystemSetting } from '@/lib/actions/settings'; +import { toast } from 'sonner'; +import { Handshake, Banknote, FileCheck } from 'lucide-react'; + +interface PartnerGovernanceProps { + config: PartnerConfig; + onUpdate: (data: PartnerConfig) => void; +} + +export function PartnerGovernanceTab({ config, onUpdate }: PartnerGovernanceProps) { + const [loading, setLoading] = useState(false); + const [state, setState] = useState(config); + + const handleSave = async () => { + setLoading(true); + try { + const res = await updateSystemSetting('partner', state); + if (res.success && res.updatedSettings.partner) { + toast.success('Partner governance rules updated'); + onUpdate(res.updatedSettings.partner); + } + } catch (error) { + toast.error('Failed to update partner settings'); + } finally { + setLoading(false); + } + }; + + const toggleKycDoc = (doc: 'pan' | 'gst' | 'aadhaar' | 'cheque') => { + const current = state.allowedKycDocs; + const updated = current.includes(doc) + ? current.filter(d => d !== doc) + : [...current, doc]; + + setState({ ...state, allowedKycDocs: updated }); + }; + + return ( +
+
+ + {/* Onboarding Rules */} + + + + + Onboarding & Approval + + + Set rules for new organizer accounts and events. + + + +
+
+ +

Block payouts until verified.

+
+ setState({ ...state, requireKyc: c })} + /> +
+
+
+ +

Admins must approve live events.

+
+ setState({ ...state, manualEventApproval: c })} + /> +
+
+
+ + {/* Commission Model */} + + + + + Commission & Payouts + + + Define the default revenue share model. + + + +
+ + setState({ ...state, defaultCommissionPercent: Number(e.target.value) })} + /> +
+
+
+ + +
+
+ + setState({ ...state, minPayoutAmount: Number(e.target.value) })} + /> +
+
+
+
+
+ + {/* KYC Requirements */} + + + + + KYC Requirements + + + Select documents required for organizer verification. + + + +
+ {['pan', 'gst', 'aadhaar', 'cheque'].map((doc) => ( +
+ toggleKycDoc(doc as any)} + /> + +
+ ))} +
+
+
+ +
+ +
+
+ ); +} diff --git a/src/features/settings/components/tabs/PaymentConfig.tsx b/src/features/settings/components/tabs/PaymentConfig.tsx new file mode 100644 index 0000000..04dab06 --- /dev/null +++ b/src/features/settings/components/tabs/PaymentConfig.tsx @@ -0,0 +1,196 @@ +'use client'; + +import { useState } from 'react'; +import { PaymentConfig, GatewayProvider, GatewayCredentials } from '@/lib/types/settings'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Label } from '@/components/ui/label'; +import { GatewayConfigSheet } from '@/features/settings/components/GatewayConfigSheet'; +import { updateRoutingRules } from '@/lib/actions/payment-settings'; +import { toast } from 'sonner'; +import { CreditCard, Globe, Settings2, ShieldCheck } from 'lucide-react'; + +interface PaymentConfigTabProps { + config: PaymentConfig; + onUpdate: (data: PaymentConfig) => void; +} + +const PROVIDERS: { id: GatewayProvider; name: string }[] = [ + { id: 'razorpay', name: 'Razorpay' }, + { id: 'stripe', name: 'Stripe' }, + { id: 'payu', name: 'PayU' }, + { id: 'easebuzz', name: 'Easebuzz' }, + { id: 'worldline', name: 'Worldline' }, +]; + +export function PaymentConfigTab({ config, onUpdate }: PaymentConfigTabProps) { + const [selectedProvider, setSelectedProvider] = useState(null); + const [sheetOpen, setSheetOpen] = useState(false); + const [routingState, setRoutingState] = useState(config); + + const handleOpenConfig = (provider: GatewayProvider) => { + setSelectedProvider(provider); + setSheetOpen(true); + }; + + const handleRoutingUpdate = async (key: keyof PaymentConfig, value: string) => { + const newState = { ...routingState, [key]: value }; + setRoutingState(newState); + + try { + const res = await updateRoutingRules({ + defaultGateway: newState.defaultGateway, + fallbackGateway: newState.fallbackGateway, + internationalGateway: newState.internationalGateway + }); + if (res.success) { + toast.success('Routing rules updated'); + // Optimistic update handled by local state, but we should propagate up + onUpdate(newState); + } + } catch (error) { + toast.error('Failed to update routing'); + } + }; + + return ( +
+ + {/* Gateway Grid */} +
+ {PROVIDERS.map((provider) => { + const gwConfig = config.gateways[provider.id]; + const isActive = gwConfig?.enabled; + const isLive = gwConfig?.mode === 'live'; + + return ( + + + {provider.name} + {isActive ? ( + + {isLive ? 'LIVE' : 'TEST'} + + ) : ( + Disabled + )} + + +
+ {isActive ? ( + <> +
+ + Verified +
+
+ {gwConfig.features.upi && UPI} + {gwConfig.features.cards && Cards} + {gwConfig.features.emi && EMI} +
+ + ) : ( +
+ Not configured +
+ )} +
+
+ + + +
+ ); + })} +
+ + {/* Routing Logic */} + + + + + Smart Routing Logic + + + Define how transactions are routed across active gateways. + + + +
+ + +

Used for 90% of traffic.

+
+ +
+ + +

Used if default fails.

+
+ +
+ + +

Cards issued outside India.

+
+
+
+ + {/* Config Sheet Instance */} + {selectedProvider && config.gateways[selectedProvider] && ( + { + // Refresh parent data? Handled via Actions and upper state update usually, + // but for now we rely on the parent's polling or manual refresh, + // effectively we might want to trigger a callback here. + // Ideally onUpdate would trigger a re-fetch. + window.location.reload(); // Quick dirty refresh to sync state in this demo + }} + /> + )} +
+ ); +} diff --git a/src/features/settings/components/tabs/PublicAppConfig.tsx b/src/features/settings/components/tabs/PublicAppConfig.tsx new file mode 100644 index 0000000..87a7ac7 --- /dev/null +++ b/src/features/settings/components/tabs/PublicAppConfig.tsx @@ -0,0 +1,193 @@ +'use client'; + +import { useState } from 'react'; +import { PublicAppConfig } from '@/lib/types/settings'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Switch } from '@/components/ui/switch'; +import { updateSystemSetting } from '@/lib/actions/settings'; +import { toast } from 'sonner'; +import { Smartphone, Zap, Percent, Link as LinkIcon } from 'lucide-react'; + +interface PublicAppConfigProps { + config: PublicAppConfig; + onUpdate: (data: PublicAppConfig) => void; +} + +export function PublicAppConfigTab({ config, onUpdate }: PublicAppConfigProps) { + const [loading, setLoading] = useState(false); + const [state, setState] = useState(config); + + const handleSave = async () => { + setLoading(true); + try { + const res = await updateSystemSetting('publicApp', state); + if (res.success && res.updatedSettings.publicApp) { + toast.success('Public App configuration updated'); + onUpdate(res.updatedSettings.publicApp); + } + } catch (error) { + toast.error('Failed to update app config'); + } finally { + setLoading(false); + } + }; + + // Helper to update deeply nested feature flags + const toggleFeature = (key: keyof PublicAppConfig['betaFeatures']) => { + setState(prev => ({ + ...prev, + betaFeatures: { + ...prev.betaFeatures, + [key]: !prev.betaFeatures[key] + } + })); + }; + + return ( +
+
+ + {/* Feature Flags */} + + + + + Feature Flags + + + Toggle beta features for end-users instantly. + + + +
+
+ +

Accept ETH/SOL via Solana Pay

+
+ toggleFeature('cryptoPayments')} + /> +
+
+
+ +

Show "Events you might like"

+
+ toggleFeature('aiRecommendations')} + /> +
+
+
+ +

Enable Google/Apple Auth

+
+ toggleFeature('socialLogin')} + /> +
+
+
+ + {/* Commercials */} + + + + + Booking Fees & Tax + + + Set the global platform fee charged to users. + + + +
+ + setState({ + ...state, + fees: { ...state.fees, platformFeeFlat: Number(e.target.value) } + })} + /> +

+ Added to every ticket purchase. +

+
+
+ + setState({ + ...state, + fees: { ...state.fees, taxRatePercent: Number(e.target.value) } + })} + /> +
+
+
+
+ + {/* Links */} + + + + + Support Links + + + Update external URLs for help, terms, and privacy policies. + + + +
+
+ + setState({ + ...state, + links: { ...state.links, helpCenterUrl: e.target.value } + })} + /> +
+
+ + setState({ + ...state, + links: { ...state.links, termsUrl: e.target.value } + })} + /> +
+
+ + setState({ + ...state, + links: { ...state.links, privacyUrl: e.target.value } + })} + /> +
+
+
+
+ +
+ +
+
+ ); +} diff --git a/src/features/settings/components/tabs/SystemHealth.tsx b/src/features/settings/components/tabs/SystemHealth.tsx new file mode 100644 index 0000000..439d13d --- /dev/null +++ b/src/features/settings/components/tabs/SystemHealth.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { useState } from 'react'; +import { SystemConfig } from '@/lib/types/settings'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; +import { Switch } from '@/components/ui/switch'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { updateSystemSetting, purgeSystemCache } from '@/lib/actions/settings'; +import { toast } from 'sonner'; +import { Server, CreditCard, Radio, Trash2, AlertTriangle, Activity } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; + +interface SystemHealthProps { + config: SystemConfig; + onUpdate: (data: SystemConfig) => void; +} + +export function SystemHealthTab({ config, onUpdate }: SystemHealthProps) { + const [loading, setLoading] = useState(false); + const [state, setState] = useState(config); + const [purgeLoading, setPurgeLoading] = useState(false); + + const handleSave = async () => { + setLoading(true); + try { + const res = await updateSystemSetting('system', state); + if (res.success && res.updatedSettings.system) { + toast.success('System configuration updated'); + onUpdate(res.updatedSettings.system); + } + } catch (error) { + toast.error('Failed to update system config'); + } finally { + setLoading(false); + } + }; + + const handlePurgeCache = async () => { + setPurgeLoading(true); + try { + const res = await purgeSystemCache(); + if (res.success) { + toast.success('Cache purged successfully', { + description: 'Changes are propagating to edge locations.' + }); + } + } catch (error) { + toast.error('Failed to purge cache'); + } finally { + setPurgeLoading(false); + } + }; + + return ( +
+ + {/* System Status Banner */} +
+ + + API Status + + +
+
+ Operational +
+

Uptime: 99.99%

+ + + + + Cache Status + + +
+ Healthy + TTL: {state.cache.ttl}s +
+

+ Last Purged: {state.cache.lastPurgedAt ? new Date(state.cache.lastPurgedAt).toLocaleTimeString() : 'Never'} +

+
+
+ + + Database + + + Connected +

Latency: 24ms

+
+
+
+ + {/* Payment Gateways */} + + + + + Payment Gateways + + + Manage gateway keys and active modes (Test vs Live). + + + + {/* Stripe */} +
+
+
+

Stripe

+ {state.gateways.stripe.mode === 'live' ? + Live : + Test Mode + } +
+ setState({ + ...state, + gateways: { ...state.gateways, stripe: { ...state.gateways.stripe, enabled: c } } + })} + /> +
+
+ + +
+
+ +
+ setState({ + ...state, + gateways: { ...state.gateways, stripe: { ...state.gateways.stripe, mode: c ? 'live' : 'test' } } + })} + /> + + {state.gateways.stripe.mode === 'live' ? 'LIVE TRAFFIC' : 'Test Mode'} + +
+
+
+ + {/* Razorpay */} +
+
+
+

Razorpay

+ {state.gateways.razorpay.mode === 'live' ? + Live : + Test Mode + } +
+ setState({ + ...state, + gateways: { ...state.gateways, razorpay: { ...state.gateways.razorpay, enabled: c } } + })} + /> +
+
+ + +
+
+
+ + + +
+ + {/* Danger Zone */} +

+ + Danger Zone +

+ +
+ + + Maintenance Mode + + Disable the public facing app temporarily. Only admins can access. + + + +
+ +

+ {false ? Active - App Offline : Inactive - App Live} +

+
+ +
+
+ + + + Cache Control + + Force purge the CDN cache for all static pages. + + + +
+ +

Items cached: ~14.2k

+
+ +
+
+
+
+ ); +} diff --git a/src/lib/actions/payment-settings.ts b/src/lib/actions/payment-settings.ts new file mode 100644 index 0000000..97983ce --- /dev/null +++ b/src/lib/actions/payment-settings.ts @@ -0,0 +1,79 @@ +import { GatewayProvider, GatewayCredentials, PaymentConfig, GlobalSettings } from '../types/settings'; +import { updateSystemSetting, getSystemSettings } from './settings'; +import { encrypt, decrypt } from '../payment-encryption'; + +export async function verifyGatewayCredentials( + provider: GatewayProvider, + credentials: Partial +): Promise<{ success: boolean; message: string }> { + // Simulate API call to provider + await new Promise(resolve => setTimeout(resolve, 1500)); + + if (provider === 'payu' && (!credentials.merchantId || !credentials.salt)) { + return { success: false, message: 'Invalid PayU credentials: Merchant ID and Salt are required.' }; + } + + if (provider === 'razorpay' && !credentials.keyId) { + return { success: false, message: 'Invalid Razorpay Key ID.' }; + } + + // Success simulation + return { success: true, message: `Successfully connected to ${provider.toUpperCase()} [${credentials.mode} mode]` }; +} + +export async function saveGatewayConfig( + provider: GatewayProvider, + config: GatewayCredentials +): Promise<{ success: boolean; message: string }> { + + // Fetch current settings first + const currentRes = await getSystemSettings(); + if (!currentRes.success) return { success: false, message: 'Failed to retrieve current settings' }; + + const settings = currentRes.data; + + // Encrypt sensitive fields + const encryptedConfig = { ...config }; + if (config.salt) encryptedConfig.salt = encrypt(config.salt); + // Note: We don't encrypt public keys usually, but secrets yes. + // For demo simplicity, we encrypt 'salt' and assume others are public/safe or handled similarly. + + // Update specific gateway in payment config + const newPaymentConfig: PaymentConfig = { + ...settings.payment, + gateways: { + ...settings.payment.gateways, + [provider]: encryptedConfig + } + }; + + // Use our main update action + const updateRes = await updateSystemSetting('payment', newPaymentConfig); + + return { + success: updateRes.success, + message: `Saved configuration for ${provider}` + }; +} + +export async function updateRoutingRules( + rules: Pick +): Promise<{ success: boolean; message: string }> { + // Fetch current settings first + const currentRes = await getSystemSettings(); + if (!currentRes.success) return { success: false, message: 'Failed to retrieve settings' }; + + const settings = currentRes.data; + + const newPaymentConfig: PaymentConfig = { + ...settings.payment, + ...rules + }; + + const updateRes = await updateSystemSetting('payment', newPaymentConfig); + + return { + success: updateRes.success, + message: 'Routing logic updated successfully' + }; +} diff --git a/src/lib/actions/settings.ts b/src/lib/actions/settings.ts new file mode 100644 index 0000000..f33203c --- /dev/null +++ b/src/lib/actions/settings.ts @@ -0,0 +1,95 @@ +import { GlobalSettings, DEFAULT_SETTINGS } from '../types/settings'; + +// Simulate a database/store with a singleton object +// In a real app, this would be a DB table 'system_config' +let MOCK_DB_SETTINGS: GlobalSettings = { ...DEFAULT_SETTINGS }; + +// Load from localStorage if available (client-side only trick for persistence in this demo) +if (typeof window !== 'undefined') { + const saved = localStorage.getItem('mock_system_settings'); + if (saved) { + try { + const parsed = JSON.parse(saved); + MOCK_DB_SETTINGS = { ...DEFAULT_SETTINGS, ...parsed }; + } catch (e) { + console.error("Failed to parse saved settings", e); + } + } +} + +export async function getSystemSettings(): Promise<{ success: boolean; data: GlobalSettings }> { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 500)); + + // In a real app, we would fetch from DB here + // return db.systemConfig.findFirst(); + + // For now, return our in-memory/local storage mock + // We try to read from localStorage again to sync tabs + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('mock_system_settings'); + if (saved) { + try { + const parsed = JSON.parse(saved); + // Deep merge or at least top-level merge to ensure new sections (like 'payment') are added + MOCK_DB_SETTINGS = { + ...DEFAULT_SETTINGS, + ...parsed, + }; + } catch (e) { /* ignore */ } + } + } + + return { success: true, data: MOCK_DB_SETTINGS }; +} + +export async function updateSystemSetting( + section: K, + data: Partial +): Promise<{ success: boolean; message: string; updatedSettings: GlobalSettings }> { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 800)); + + // Update the specific section + MOCK_DB_SETTINGS = { + ...MOCK_DB_SETTINGS, + [section]: { + ...MOCK_DB_SETTINGS[section], + ...data + } + }; + + // Persist to localStorage for demo + if (typeof window !== 'undefined') { + localStorage.setItem('mock_system_settings', JSON.stringify(MOCK_DB_SETTINGS)); + } + + console.log(`[AUDIT] System Setting Updated: ${section}`, data); + + // In Next.js we would maintain revalidatePath here + // revalidatePath('/settings'); + + return { + success: true, + message: 'Settings updated successfully.', + updatedSettings: MOCK_DB_SETTINGS + }; +} + +export async function purgeSystemCache(): Promise<{ success: boolean; message: string }> { + await new Promise(resolve => setTimeout(resolve, 1500)); + + console.log(`[AUDIT] System Cache Purged by Admin`); + + // Update last purged time + MOCK_DB_SETTINGS.system.cache.lastPurgedAt = new Date().toISOString(); + + if (typeof window !== 'undefined') { + localStorage.setItem('mock_system_settings', JSON.stringify(MOCK_DB_SETTINGS)); + } + + return { + success: true, + message: 'System cache purged. Changes are now live on Public App.' + }; +} diff --git a/src/lib/payment-encryption.ts b/src/lib/payment-encryption.ts new file mode 100644 index 0000000..a47a8b1 --- /dev/null +++ b/src/lib/payment-encryption.ts @@ -0,0 +1,33 @@ +// In a real Node.js environment, we would use 'crypto' +// import crypto from 'crypto'; + +// Since this is a Vite client-side demo, we'll mock the encryption +// to simulate the security layer. + +export function encrypt(text: string): string { + if (!text) return ''; + try { + // Simple base64 encoding simulation + prefix + return `enc_${btoa(text)}`; + } catch (e) { + console.error("Encryption failed", e); + return text; + } +} + +export function decrypt(text: string): string { + if (!text) return ''; + if (!text.startsWith('enc_')) return text; // Return as-is if not encrypted + try { + const payload = text.replace('enc_', ''); + return atob(payload); + } catch (e) { + console.error("Decryption failed", e); + return text; + } +} + +export function maskKey(key: string): string { + if (!key || key.length < 8) return '********'; + return `******${key.slice(-4)}`; +} diff --git a/src/lib/types/settings.ts b/src/lib/types/settings.ts new file mode 100644 index 0000000..45ad0b2 --- /dev/null +++ b/src/lib/types/settings.ts @@ -0,0 +1,173 @@ +export interface OrganizationConfig { + brandName: string; + supportEmail: string; + legalAddress: string; + logoUrl?: string; // For emails + socialLinks: { + twitter?: string; + linkedin?: string; + instagram?: string; + }; +} + +export interface SecurityConfig { + enforce2FA: boolean; + sessionTimeoutMinutes: number; + passwordExpirationDays: number; +} + +export interface PublicAppConfig { + maintenanceMode: boolean; + betaFeatures: { + cryptoPayments: boolean; + aiRecommendations: boolean; + socialLogin: boolean; + }; + fees: { + platformFeeFlat: number; // e.g. 20 (INR) + taxRatePercent: number; // e.g. 18 (GST) + }; + banners: { + id: string; + imageUrl: string; + linkUrl: string; + active: boolean; + }[]; + links: { + helpCenterUrl: string; + termsUrl: string; + privacyUrl: string; + }; +} + +export interface PartnerConfig { + requireKyc: boolean; + manualEventApproval: boolean; + defaultCommissionPercent: number; + payoutSchedule: 'daily' | 'weekly' | 'monthly' | 'manual'; + minPayoutAmount: number; + allowedKycDocs: ('pan' | 'gst' | 'aadhaar' | 'cheque')[]; +} + +export type GatewayProvider = 'razorpay' | 'stripe' | 'payu' | 'easebuzz' | 'worldline'; + +export interface GatewayCredentials { + enabled: boolean; + mode: 'test' | 'live'; + merchantId?: string; + publicKey?: string; // For Stripe + keyId?: string; // For Razorpay + salt?: string; // For PayU/Easebuzz + webhookSecret?: string; + features: { + netbanking: boolean; + upi: boolean; + cards: boolean; + emi: boolean; + wallet: boolean; + }; +} + +export interface PaymentConfig { + defaultGateway: GatewayProvider; + fallbackGateway: GatewayProvider; + internationalGateway: GatewayProvider; + gateways: Record; +} + +export interface GlobalSettings { + organization: OrganizationConfig; + security: SecurityConfig; + publicApp: PublicAppConfig; + partner: PartnerConfig; + system: SystemConfig; + payment: PaymentConfig; // New section +} + +export const DEFAULT_SETTINGS: GlobalSettings = { + organization: { + brandName: 'Eventify', + supportEmail: 'support@eventify.com', + legalAddress: '123 Tech Park, Bangalore, India', + socialLinks: {} + }, + security: { + enforce2FA: false, + sessionTimeoutMinutes: 60, + passwordExpirationDays: 90 + }, + publicApp: { + maintenanceMode: false, + betaFeatures: { + cryptoPayments: false, + aiRecommendations: true, + socialLogin: true + }, + fees: { + platformFeeFlat: 20, + taxRatePercent: 18 + }, + banners: [], + links: { + helpCenterUrl: 'https://help.eventify.com', + termsUrl: 'https://eventify.com/terms', + privacyUrl: 'https://eventify.com/privacy' + } + }, + partner: { + requireKyc: true, + manualEventApproval: false, + defaultCommissionPercent: 5, + payoutSchedule: 'weekly', + minPayoutAmount: 1000, + allowedKycDocs: ['pan', 'gst', 'cheque'] + }, + system: { + gateways: { // Legacy structure, will migrate to 'payment' + stripe: { enabled: true, mode: 'test', publicKey: 'pk_test_...' }, + razorpay: { enabled: true, mode: 'test', keyId: 'rzp_test_...' } + }, + cache: { + ttl: 3600 + } + }, + payment: { + defaultGateway: 'razorpay', + fallbackGateway: 'stripe', + internationalGateway: 'stripe', + gateways: { + razorpay: { + enabled: true, + mode: 'test', + keyId: 'rzp_test_123456', + features: { netbanking: true, upi: true, cards: true, emi: false, wallet: true } + }, + stripe: { + enabled: true, + mode: 'test', + publicKey: 'pk_test_123456', + features: { netbanking: false, upi: false, cards: true, emi: false, wallet: false } + }, + payu: { + enabled: false, + mode: 'test', + merchantId: '', + salt: '', + features: { netbanking: true, upi: true, cards: true, emi: true, wallet: true } + }, + easebuzz: { + enabled: false, + mode: 'test', + merchantId: '', + salt: '', + features: { netbanking: true, upi: true, cards: true, emi: true, wallet: true } + }, + worldline: { + enabled: false, + mode: 'test', + merchantId: '', + features: { netbanking: true, upi: true, cards: true, emi: false, wallet: false } + } + } + } +}; diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 5bbebc2..9055e17 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,32 +1,10 @@ import { AppLayout } from '@/components/layout/AppLayout'; -import { OrganizationProfileCard } from '@/features/settings/components/OrganizationProfileCard'; -import { PayoutBankingCard } from '@/features/settings/components/PayoutBankingCard'; -import { TeamSecurityCard } from '@/features/settings/components/TeamSecurityCard'; -import { DeveloperSection } from '@/features/settings/components/DeveloperSection'; -import { PaymentGatewayCard } from '@/features/settings/components/PaymentGatewayCard'; +import { SettingsLayout } from '@/features/settings/components/SettingsLayout'; export default function Settings() { return ( - -
- {/* Top Grid: Identity & Banking */} -
- - -
- - {/* Middle Grid: Gateways & Security */} -
- - -
- - {/* Collapsible Developer Section */} - -
+ + ); }