diff --git a/apps/web/app/brand-dashboard/layout.tsx b/apps/web/app/brand-dashboard/layout.tsx index 80a11c8..70a3009 100644 --- a/apps/web/app/brand-dashboard/layout.tsx +++ b/apps/web/app/brand-dashboard/layout.tsx @@ -25,7 +25,7 @@ export default function BrandDashboardLayout({ }) { const pathname = usePathname(); const [isSidebarOpen, setIsSidebarOpen] = useState(false); - const { counts, fetchCounts } = useDashboardStore(); + const { counts, fetchCounts, isChatActive } = useDashboardStore(); useEffect(() => { fetchCounts(); @@ -106,21 +106,23 @@ export default function BrandDashboardLayout({ {/* Main Header & Content Area */}
{/* Dashboard Header */} - setIsSidebarOpen(true)} - > -
- - Explore Gigs - -
-
+
+ setIsSidebarOpen(true)} + > +
+ + Explore gigs + +
+
+
(null); const { user } = useAuthStore(); const currentUserId = user?.id; + const { setIsChatActive } = useDashboardStore(); + + useEffect(() => { + setIsChatActive(!!selectedConvId); + return () => setIsChatActive(false); + }, [selectedConvId, setIsChatActive]); + + const handleSelectConv = (id: string | null) => { + setSelectedConvId(id); + if (id) { + router.push(`${pathname}?gigRequestId=${id}`); + } else { + router.push(pathname); + } + }; // Fallback for historical gig requests with no existing messages useEffect(() => { @@ -107,18 +125,13 @@ function MessagesContent() { }; return ( -
-
+
+
{/* Sidebar */} -
-
-
-

Messages

- -
+
+
+

Messages

@@ -152,10 +165,10 @@ function MessagesContent() { ) : filteredConvs.map((conv) => ( +

Booking Details

+ {/* Brand & Campaign Header */} +
+
+
+ {selectedBooking.brandProfile?.profileImageUrl ? ( + { (e.target as HTMLImageElement).style.display = 'none'; }} + /> + ) : ( + (selectedBooking.brandProfile?.companyName || "B").charAt(0) + )} +
+
+ +

{selectedBooking.brandProfile?.companyName || "Unknown Brand"}

+ +

{selectedBooking.gigId?.title || "Campaign Booking"}

+ {selectedBooking.gigId?.description && ( +

{selectedBooking.gigId.description}

+ )} +
+ + {getStatusLabel(selectedBooking.status)} + +
+
{/* Details List */}
@@ -335,7 +426,7 @@ function BookingsContent() { Booking Lifecycle

-
+
{[ { label: "Request Accepted", isCompleted: true }, { label: "Funds in Escrow", isCompleted: selectedBooking.status === "IN_ESCROW" || selectedBooking.status === "COMPLETED" }, @@ -343,12 +434,16 @@ function BookingsContent() { { label: "Work Approved", isCompleted: selectedBooking.workStatus === "APPROVED" || selectedBooking.status === "COMPLETED" }, { label: "Funds Released", isCompleted: selectedBooking.status === "COMPLETED" }, ].map((step, idx) => ( -
-
+
- + {step.isCompleted ? ( + + ) : ( +
+ )}

{step.label}

@@ -421,7 +516,7 @@ function BookingsContent() {
-
+
{weekDays.map(day => (
{day} @@ -139,16 +139,16 @@ export default function CalendarPage() { ))}
-
+
{Array.from({ length: firstDayOfMonth(currentDate.getFullYear(), currentDate.getMonth()) }).map((_, i) => ( -
+
))} {days.map((d) => (
-
+

TOTAL EARNED

-
-

₹{stats.total.toLocaleString()}

+
+

₹{stats.total.toLocaleString()}

-
+

IN ESCROW

-
-

₹{stats.escrow.toLocaleString()}

+
+

₹{stats.escrow.toLocaleString()}

-
+

THIS MONTH

-
-

₹{stats.thisMonth.toLocaleString()}

+
+

₹{stats.thisMonth.toLocaleString()}

@@ -235,20 +235,21 @@ export default function EarningsPage() { {/* Main Content Grid */}
{/* Left Side: Table */} -
-
+
+

Recent Transactions

-
+
-
+ {/* Desktop View: Table */} +
@@ -293,81 +294,130 @@ export default function EarningsPage() {
-
- {/* Right Side: Detail Sidecard */} - {selectedOrder && ( -
-
-
- + {/* Mobile View: Cards Feed List */} +
+ {orders.map((order) => ( +
setSelectedOrderId(order._id)} + className={`p-4 rounded-3xl border transition-all cursor-pointer ${ + selectedOrderId === order._id + ? "bg-emerald-50/40 border-emerald-200 shadow-sm" + : "bg-white border-gray-100 hover:border-gray-200 shadow-sm" + }`} + > +
+ + {order.gigId?.title || "Influencer Booking"} + + + {order.status} + +
+
+ {new Date(order.createdAt).toLocaleDateString()} + ₹{(order.influencerAmount || 0).toLocaleString()} +
-

{selectedOrder.gigId?.title || "Influencer Booking"}

-

Order Details

- -
- PAYOUT: {selectedOrder.payoutStatus} + ))} + {orders.length === 0 && ( +
+ No transactions found
-
+ )} +
+
-
- {/* Payout Info */} -
-

- Status Tracking -

-
-
-
-
- -
-
-

Payment Received

-

{new Date(selectedOrder.createdAt).toLocaleDateString()}

-
-
-
-
- -
-
-

Escrow Released

-
+ {/* Right Side: Detail Sidecard */} +
+
+ {selectedOrderId && selectedOrder ? ( + <> +
+ {/* Back Button on mobile */} + +
+
-
-
- -
-
-

Final Payout

-
+

{selectedOrder.gigId?.title || "Influencer Booking"}

+

Order Details

+ +
+ PAYOUT: {selectedOrder.payoutStatus}
-
- {/* Financial Breakdown */} -
-

Financial Breakdown

-
-
- Gross Price - ₹{(selectedOrder.amount || 0).toLocaleString()} -
-
- Platform Fee (5%) - -₹{(selectedOrder.platformFee || 0).toLocaleString()} +
+ {/* Payout Info */} +
+

+ Status Tracking +

+
+
+
+
+ +
+
+

Payment Received

+

{new Date(selectedOrder.createdAt).toLocaleDateString()}

+
+
+
+
+ +
+
+

Escrow Released

+
+
+
+
+ +
+
+

Final Payout

+
+
+
-
-
- Your Earning - ₹{(selectedOrder.influencerAmount || 0).toLocaleString()} + + {/* Financial Breakdown */} +
+

Financial Breakdown

+
+
+ Gross Price + ₹{(selectedOrder.amount || 0).toLocaleString()} +
+
+ Platform Fee (5%) + -₹{(selectedOrder.platformFee || 0).toLocaleString()} +
+
+
+ Your Earning + ₹{(selectedOrder.influencerAmount || 0).toLocaleString()} +
+
+ + ) : ( +
+ Select an order to see details
-
+ )}
- )} +
); diff --git a/apps/web/app/influencer-dashboard/layout.tsx b/apps/web/app/influencer-dashboard/layout.tsx index 0a53992..8e15265 100644 --- a/apps/web/app/influencer-dashboard/layout.tsx +++ b/apps/web/app/influencer-dashboard/layout.tsx @@ -26,7 +26,7 @@ export default function InfluencerDashboardLayout({ }) { const pathname = usePathname(); const [isSidebarOpen, setIsSidebarOpen] = useState(false); - const { counts, fetchCounts } = useDashboardStore(); + const { counts, fetchCounts, isChatActive } = useDashboardStore(); useEffect(() => { fetchCounts(); @@ -107,21 +107,23 @@ export default function InfluencerDashboardLayout({ {/* Main Area */}
{/* Dashboard Header */} - setIsSidebarOpen(true)} - > -
- - Create gig - -
-
+
+ setIsSidebarOpen(true)} + > +
+ + Create gig + +
+
+
{/* Content */}
(null); const { user } = useAuthStore(); const currentUserId = user?.id; + const { setIsChatActive } = useDashboardStore(); + + useEffect(() => { + setIsChatActive(!!selectedConvId); + return () => setIsChatActive(false); + }, [selectedConvId, setIsChatActive]); + + const handleSelectConv = (id: string | null) => { + setSelectedConvId(id); + if (id) { + router.push(`${pathname}?gigRequestId=${id}`); + } else { + router.push(pathname); + } + }; // Fallback for historical gig requests with no existing messages useEffect(() => { @@ -109,11 +127,11 @@ function MessagesContent() { }; return ( -
-
+
+
{/* Sidebar */} -
+

Messages

@@ -144,7 +162,7 @@ function MessagesContent() { ) : filteredConvs.map((conv) => ( +
-
+ {/* Desktop Table View */} +
@@ -214,6 +223,31 @@ export default function InfluencerDashboardPage() {
+ + {/* Mobile Card View */} +
+ {recentBookings.map((booking) => ( +
+
+
+ {booking.gigId?.title?.charAt(0) || "B"} +
+
+ {booking.gigId?.title || "Influencer Booking"} + ₹{(booking.influencerAmount || 0).toLocaleString()} +
+
+ + {getStatusLabel(booking.status)} + +
+ ))} + {recentBookings.length === 0 && ( +
+ No bookings found +
+ )} +
{/* Dynamic Calendar & Aside Card */} @@ -273,9 +307,9 @@ export default function InfluencerDashboardPage() { key={day} onClick={() => setSelectedDate(dateString)} className={`flex justify-center items-center h-10 w-10 mx-auto text-[13px] font-bold cursor-pointer rounded-2xl relative transition-all ${isSelected - ? "bg-gray-900 text-white shadow-lg shadow-gray-200 scale-110" + ? "bg-emerald-600 text-white shadow-md shadow-emerald-100 scale-105" : hasEvent - ? "bg-emerald-50 text-emerald-600 hover:bg-emerald-100" + ? "bg-emerald-50 text-emerald-600 hover:bg-emerald-100/80" : "text-gray-900 hover:bg-gray-50" }`} > @@ -294,7 +328,7 @@ export default function InfluencerDashboardPage() { {!selectedDate ? (
- +

Timeline Detail

Select a date to view
your direct milestones

@@ -347,8 +381,12 @@ export default function InfluencerDashboardPage() {
)) ) : ( -
-

No Milestones

+
+
+ +
+

No Milestones Today

+

Enjoy your free day!

)}
diff --git a/apps/web/app/influencer-dashboard/requests/page.tsx b/apps/web/app/influencer-dashboard/requests/page.tsx index 6e48740..3ba3012 100644 --- a/apps/web/app/influencer-dashboard/requests/page.tsx +++ b/apps/web/app/influencer-dashboard/requests/page.tsx @@ -3,8 +3,7 @@ import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; -import { Search, ChevronRight, Calendar, Globe, Loader2 } from "lucide-react"; -import Image from "next/image"; +import { Search, ChevronLeft, ChevronRight, Calendar, Globe, Loader2 } from "lucide-react"; import api from "@/lib/axios.client"; import { useDashboardStore } from "@/store/dashboard.store"; @@ -59,7 +58,9 @@ export default function RequestsPage() { useEffect(() => { if (filteredRequests.length > 0 && !selectedRequestId) { - setSelectedRequestId(filteredRequests[0]._id); + if (typeof window !== "undefined" && window.innerWidth >= 1280) { + setSelectedRequestId(filteredRequests[0]._id); + } } }, [filteredRequests, selectedRequestId]); @@ -140,8 +141,9 @@ export default function RequestsPage() { {/* Content Container (2-Column Grid) */}
{/* Table Card (Left Column) */} -
-
+
+ {/* Desktop View: Table */} +
@@ -160,12 +162,25 @@ export default function RequestsPage() { >
-
- {req.brandId?.profileImageUrl ? ( - {req.brandId.fullName} - ) : ( - req.brandId?.fullName?.charAt(0) || "B" +
+ {req.brandId?.profileImageUrl && ( + {req.brandId.fullName} { + e.currentTarget.style.display = 'none'; + const fallback = e.currentTarget.nextSibling as HTMLDivElement; + if (fallback) fallback.style.display = 'flex'; + }} + /> )} +
+ {req.brandId?.fullName?.charAt(0) || "B"} +
{req.brandId?.fullName || "Brand User"} @@ -191,25 +206,109 @@ export default function RequestsPage() {
+ + {/* Mobile/Tablet View: Card-based list */} +
+ {filteredRequests.map((req) => ( +
setSelectedRequestId(req._id)} + className={`p-4 rounded-3xl border transition-all cursor-pointer ${ + selectedRequestId === req._id + ? "bg-emerald-50/40 border-emerald-200 shadow-sm" + : "bg-white border-gray-100 hover:border-gray-200 shadow-sm" + }`} + > +
+
+
+ {req.brandId?.profileImageUrl && ( + {req.brandId.fullName} { + e.currentTarget.style.display = 'none'; + const fallback = e.currentTarget.nextSibling as HTMLDivElement; + if (fallback) fallback.style.display = 'flex'; + }} + /> + )} +
+ {req.brandId?.fullName?.charAt(0) || "B"} +
+
+
+ + {req.brandId?.fullName || "Brand User"} + + + {activeFilter} + +
+
+ +
+
+
+ Gig Request + {req.gigId?.title || "Gig Request"} +
+
+ Net Amount + ₹{((req.gigId?.pricing?.basePrice || 0) * 0.9).toLocaleString()} +
+
+
+ ))} + {filteredRequests.length === 0 && ( +
+ No {activeFilter.toLowerCase()} requests found. +
+ )} +
{/* Details Card (Right Column) */} -
-
+
+
-

Request Details

- - {selectedRequest ? ( + {selectedRequestId && selectedRequest ? (
-
+ {/* Back Button on mobile */} + +

Request Details

+
{/* Brand Profile */}
-
- {selectedRequest.brandId?.profileImageUrl ? ( - {selectedRequest.brandId.fullName} - ) : ( - selectedRequest.brandId?.fullName?.charAt(0) || "B" +
+ {selectedRequest.brandId?.profileImageUrl && ( + {selectedRequest.brandId.fullName} { + e.currentTarget.style.display = 'none'; + const fallback = e.currentTarget.nextSibling as HTMLDivElement; + if (fallback) fallback.style.display = 'flex'; + }} + /> )} +
+ {selectedRequest.brandId?.fullName?.charAt(0) || "B"} +

{selectedRequest.brandId?.fullName || "Brand User"}

diff --git a/apps/web/components/NotificationBell.tsx b/apps/web/components/NotificationBell.tsx index cdd4c9c..d40b2fa 100644 --- a/apps/web/components/NotificationBell.tsx +++ b/apps/web/components/NotificationBell.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { Bell, X } from "lucide-react"; +import { Bell } from "lucide-react"; import { useRouter } from "next/navigation"; import { useNotificationStore, Notification } from "@/store/notification.store"; @@ -16,9 +16,9 @@ export default function NotificationBell() { const mountedRef = useRef(false); -useEffect(() => { - mountedRef.current = true; -}, []); + useEffect(() => { + mountedRef.current = true; + }, []); // Fetch notifications on mount if user is logged in useEffect(() => { @@ -58,11 +58,6 @@ useEffect(() => { markAsRead(notification._id); } - // TEST ROUTER PUSH (Uncomment if needed) - // console.log("🔥 TEST ROUTER PUSH"); - // router.push("/influencer-dashboard"); - // return; - const type = notification?.type?.toUpperCase(); if (type === "GIG_REQUEST") { @@ -87,7 +82,6 @@ useEffect(() => { } else { console.warn("❌ Unknown navigation type", notification); - // Fallback setIsOpen(false); } }; @@ -121,74 +115,61 @@ useEffect(() => { {isOpen && ( - <> - {/* Mobile backdrop */} -
setIsOpen(false)} /> - -
-
-

Notifications

-
- {unreadCount > 0 && ( - - {unreadCount} new - - )} - +
+
+

Notifications

+ {unreadCount > 0 && ( + + {unreadCount} new + + )} +
+ +
+ {loading && notifications.length === 0 ? ( +
+
-
- -
- {loading && notifications.length === 0 ? ( -
-
+ ) : notifications.length === 0 ? ( +
+
+
- ) : notifications.length === 0 ? ( -
-
- -
-

No notifications yet

-

We'll let you know when something happens.

-
- ) : ( -
    - {notifications.map((notification) => ( -
  • handleNotificationClick(notification)} - className={`p-5 transition-all cursor-pointer group ${!notification.read ? "bg-white hover:bg-emerald-50/30" : "bg-transparent hover:bg-white" - }`} - > -
    -
    - {notification.read ? ( -
    - ) : ( -
    - )} -
    -
    -

    - {notification.message} -

    -

    - {formatTime(notification.createdAt)} -

    -
    +

    No notifications yet

    +

    We'll let you know when something happens.

    +
    + ) : ( +
      + {notifications.map((notification) => ( +
    • handleNotificationClick(notification)} + className={`px-5 py-4 transition-colors cursor-pointer group ${!notification.read ? "bg-green-50/30 hover:bg-green-50/50" : "hover:bg-gray-50"}`} + > +
      +
      + {!notification.read && ( +
      + )} +
      +
      +

      + {notification.message} +

      +

      + {formatTime(notification.createdAt)} +

      -
    • - ))} -
    - )} -
+
+ + ))} + + )}
- +
)}
); diff --git a/apps/web/components/chat/ChatWindow.tsx b/apps/web/components/chat/ChatWindow.tsx index b240699..0038e44 100644 --- a/apps/web/components/chat/ChatWindow.tsx +++ b/apps/web/components/chat/ChatWindow.tsx @@ -412,11 +412,14 @@ export function ChatWindow({ return (
{/* Header */} -
-
+
+
{onBack && ( - )}
diff --git a/apps/web/components/chat/MessageBubble.tsx b/apps/web/components/chat/MessageBubble.tsx index aeedecd..5e17167 100644 --- a/apps/web/components/chat/MessageBubble.tsx +++ b/apps/web/components/chat/MessageBubble.tsx @@ -255,35 +255,34 @@ export function MessageBubble({ message, currentUserId, isBrand, onRespond, onRe )}
) : isDeliverable && dData ? ( -
-
-
- Final Deliverable - - {dData.status} - -
- - {/* Secure Media Viewer */} -
- -
+
+
+ Final Deliverable + + {dData.status} + +
- {dData.status === "REJECTED" && dData.rejectionNote && ( -
-

Feedback

-

“{dData.rejectionNote}”

-
- )} + {/* Secure Media Viewer */} +
+
+ {dData.status === "REJECTED" && dData.rejectionNote && ( +
+

Feedback

+

“{dData.rejectionNote}”

+
+ )} + {dData.status === "PENDING" && isBrand && (

Action Required

diff --git a/apps/web/components/shared/SecureMediaPreview.tsx b/apps/web/components/shared/SecureMediaPreview.tsx index ffacde0..8d2c32c 100644 --- a/apps/web/components/shared/SecureMediaPreview.tsx +++ b/apps/web/components/shared/SecureMediaPreview.tsx @@ -6,12 +6,16 @@ import NextImage from "next/image"; import { SecureMediaModal } from "./SecureMediaModal"; +import { cn } from "@/lib/utils"; + + interface SecureMediaPreviewProps { url: string; type?: "image" | "video"; + className?: string; } -export function SecureMediaPreview({ url, type = "image" }: SecureMediaPreviewProps) { +export function SecureMediaPreview({ url, type = "image", className }: SecureMediaPreviewProps) { const [isVisible, setIsVisible] = useState(true); const [isModalOpen, setIsModalOpen] = useState(false); const containerRef = useRef(null); @@ -50,7 +54,7 @@ export function SecureMediaPreview({ url, type = "image" }: SecureMediaPreviewPr
setIsModalOpen(true)} - className="relative group w-full aspect-video bg-slate-900 rounded-2xl overflow-hidden shadow-2xl border border-slate-800 cursor-zoom-in" + className={cn("relative group w-full aspect-video bg-slate-900 rounded-2xl overflow-hidden shadow-2xl border border-slate-800 cursor-zoom-in", className)} > {!isVisible && (
diff --git a/apps/web/store/dashboard.store.ts b/apps/web/store/dashboard.store.ts index d7dd614..5378ee5 100644 --- a/apps/web/store/dashboard.store.ts +++ b/apps/web/store/dashboard.store.ts @@ -11,6 +11,8 @@ interface DashboardStore { counts: DashboardCounts; loading: boolean; fetchCounts: () => Promise; + isChatActive: boolean; + setIsChatActive: (active: boolean) => void; } export const useDashboardStore = create((set) => ({ @@ -19,6 +21,9 @@ export const useDashboardStore = create((set) => ({ pendingRequestsCount: 0, }, loading: false, + isChatActive: false, + + setIsChatActive: (active) => set({ isChatActive: active }), fetchCounts: async () => { set({ loading: true });