-
Notifications
You must be signed in to change notification settings - Fork 139
feat(ui): Collaboration Hub #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…on dashboard UI (frontend)
…bars, timeline, and latest update (frontend)
|
""" WalkthroughNew React components and mock data were added to implement an AI-powered Creator Collaboration Hub interface. These include creator match cards, collaboration grids, profile and connect modals, and active collaboration tracking. The Collaborations page and Dashboard were updated to use these components, replacing static content with dynamic, reusable UI elements and mock data. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant DashboardPage
participant CollaborationsPage
participant CreatorMatchGrid
participant CreatorMatchCard
participant ActiveCollabsGrid
participant ActiveCollabCard
User->>DashboardPage: Navigates to dashboard
DashboardPage->>CollaborationsPage: Renders CollaborationsPage (with/without header)
CollaborationsPage->>CreatorMatchGrid: Renders AI Matches tab
CreatorMatchGrid->>CreatorMatchCard: Renders creator match cards
CollaborationsPage->>ActiveCollabsGrid: Renders Active Collabs tab
ActiveCollabsGrid->>ActiveCollabCard: Renders active collaboration cards
CreatorMatchCard->>ViewProfileModal: (Optional) Opens profile modal
CreatorMatchCard->>ConnectModal: (Optional) Opens connect modal
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Nitpick comments (11)
Frontend/src/components/collaboration-hub/activeCollabsMockData.ts (1)
1-52: Excellent mock data structure for collaboration tracking.The mock data provides a comprehensive structure for active collaborations with good variety in status, progress, and timing. The deliverables tracking with completed/total counts is particularly well-designed.
Consider using enum for status values.
For better type safety and consistency, consider defining status values as an enum or union type:
type CollabStatus = "In Progress" | "Awaiting Response" | "Completed" | "On Hold";Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (1)
26-40: Consider using UI component library for consistency.The pagination buttons use inline styling. For consistency with the rest of the application, consider using the Button component from your UI library:
- <button - className="px-4 py-2 rounded bg-gray-100 text-gray-700 disabled:opacity-50" - onClick={() => setPage((p) => Math.max(p - 1, 0))} - disabled={page === 0} - > + <Button + variant="secondary" + onClick={() => setPage((p) => Math.max(p - 1, 0))} + disabled={page === 0} + >Frontend/src/components/collaboration-hub/ActiveCollabsGrid.tsx (1)
12-23: Consider memoizing the filtering and sorting logic.The filtering and sorting operations run on every render, which could impact performance with larger datasets. Consider using
useMemoto optimize this logic.+import React, { useState, useMemo } from "react"; - // Only show In Progress and Completed - let filtered = activeCollabsMock.filter(c => c.status !== "Awaiting Response"); - if (statusFilter !== "All") { - filtered = filtered.filter(c => c.status === statusFilter); - } - if (sortBy === "Start Date") { - filtered = [...filtered].sort((a, b) => a.startDate.localeCompare(b.startDate)); - } else if (sortBy === "Due Date") { - filtered = [...filtered].sort((a, b) => a.dueDate.localeCompare(b.dueDate)); - } else if (sortBy === "Name") { - filtered = [...filtered].sort((a, b) => a.collaborator.name.localeCompare(b.collaborator.name)); - } + const filtered = useMemo(() => { + let result = activeCollabsMock.filter(c => c.status !== "Awaiting Response"); + if (statusFilter !== "All") { + result = result.filter(c => c.status === statusFilter); + } + if (sortBy === "Start Date") { + result = [...result].sort((a, b) => a.startDate.localeCompare(b.startDate)); + } else if (sortBy === "Due Date") { + result = [...result].sort((a, b) => a.dueDate.localeCompare(b.dueDate)); + } else if (sortBy === "Name") { + result = [...result].sort((a, b) => a.collaborator.name.localeCompare(b.collaborator.name)); + } + return result; + }, [statusFilter, sortBy]);Frontend/src/pages/Collaborations.tsx (1)
13-16: Consider organizing imports by type.The imports could be better organized for improved readability and maintainability.
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card" import { ModeToggle } from "../components/mode-toggle" import { UserNav } from "../components/user-nav" import { Button } from "../components/ui/button" import { Input } from "../components/ui/input" import { BarChart3, Briefcase, FileText, LayoutDashboard, MessageSquare, Rocket, Search, Users } from "lucide-react" -import {Link} from "react-router-dom" import { Avatar, AvatarFallback, AvatarImage } from "../components/ui/avatar" import { Badge } from "../components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs" import { Label } from "../components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select" +import React from "react"; +import {Link} from "react-router-dom" + +// Collaboration hub components import CreatorMatchGrid from "../components/collaboration-hub/CreatorMatchGrid"; import { mockCreatorMatches } from "../components/dashboard/creator-collaborations"; import ActiveCollabsGrid from "../components/collaboration-hub/ActiveCollabsGrid"; -import React from "react";Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx (1)
56-59: Consider memoizing expensive calculations.The progress calculations run on every render. Consider using
useMemofor better performance.+import React, { useMemo } from "react"; const ActiveCollabCard: React.FC<ActiveCollabCardProps> = ({ // ... props }) => { - const deliverableProgress = Math.round((deliverables.completed / deliverables.total) * 100); - const timelineProgress = getTimelineProgress(startDate, dueDate); - const daysLeft = getDaysLeft(dueDate); - const overdue = daysLeft < 0 && status !== "Completed"; + const { deliverableProgress, timelineProgress, daysLeft, overdue } = useMemo(() => ({ + deliverableProgress: Math.round((deliverables.completed / deliverables.total) * 100), + timelineProgress: getTimelineProgress(startDate, dueDate), + daysLeft: getDaysLeft(dueDate), + overdue: getDaysLeft(dueDate) < 0 && status !== "Completed" + }), [deliverables, startDate, dueDate, status]);Frontend/src/components/collaboration-hub/ViewProfileModal.tsx (1)
47-55: Consider extracting social media icons to separate components.The inline SVG rendering could be improved by extracting icons to reusable components for better maintainability.
Create a separate icons component:
// components/ui/social-icons.tsx export const InstagramIcon = ({ className }: { className?: string }) => ( <svg className={className} fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <rect width="20" height="20" x="2" y="2" rx="5"/> <path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/> <line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/> </svg> ); // Similar for YouTube and Twitter iconsThen use in the component:
+import { InstagramIcon, YouTubeIcon, TwitterIcon } from "../ui/social-icons"; - {link.icon === "instagram" && ( - <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><rect width="20" height="20" x="2" y="2" rx="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" x2="17.51" y1="6.5" y2="6.5"/></svg> - )} + {link.icon === "instagram" && <InstagramIcon className="w-5 h-5" />}Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (5)
8-19: Consider making avatar prop optional and adding JSDoc.The interface is comprehensive, but consider these improvements:
+/** + * Props for the CreatorMatchCard component + */ export interface CreatorMatchCardProps { name: string; - avatar: string; + avatar?: string; contentType: string; matchPercentage: number; audienceMatch: string; followers: string; engagement: string; content: string; collabs: number; whyMatch: string[]; }
21-32: Extract color constants for better maintainability.The function logic is correct, but consider extracting the colors as constants:
+const AUDIENCE_MATCH_COLORS = { + 'Very High': 'bg-green-500', + 'High': 'bg-yellow-400', + 'Good': 'bg-blue-400', + 'default': 'bg-gray-300' +} as const; + const getAudienceMatchColor = (level: string) => { - switch (level) { - case "Very High": - return "bg-green-500"; - case "High": - return "bg-yellow-400"; - case "Good": - return "bg-blue-400"; - default: - return "bg-gray-300"; - } + return AUDIENCE_MATCH_COLORS[level as keyof typeof AUDIENCE_MATCH_COLORS] || AUDIENCE_MATCH_COLORS.default; };
50-54: Extract timeout duration as a constant.+const SUCCESS_MESSAGE_DURATION = 2000; + const handleSendRequest = () => { setShowConnect(false); setRequestSent(true); - setTimeout(() => setRequestSent(false), 2000); + setTimeout(() => setRequestSent(false), SUCCESS_MESSAGE_DURATION); };
74-75: Extract audience match width calculation logic.The inline style logic is complex and could be extracted for better readability:
+const getAudienceMatchWidth = (level: string) => { + switch (level) { + case "Very High": return "100%"; + case "High": return "75%"; + case "Good": return "50%"; + default: return "25%"; + } +}; + -<div className={`h-2 rounded ${getAudienceMatchColor(audienceMatch)}`} style={{ width: audienceMatch === "Very High" ? "100%" : audienceMatch === "High" ? "75%" : "50%" }} /> +<div + className={`h-2 rounded ${getAudienceMatchColor(audienceMatch)}`} + style={{ width: getAudienceMatchWidth(audienceMatch) }} +/>
104-104: Extract modal transition delay as a constant.+const MODAL_TRANSITION_DELAY = 200; + onConnect={() => { setShowProfile(false); - setTimeout(() => setShowConnect(true), 200); + setTimeout(() => setShowConnect(true), MODAL_TRANSITION_DELAY); }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx(1 hunks)Frontend/src/components/collaboration-hub/ActiveCollabsGrid.tsx(1 hunks)Frontend/src/components/collaboration-hub/ConnectModal.tsx(1 hunks)Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx(1 hunks)Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx(1 hunks)Frontend/src/components/collaboration-hub/ViewProfileModal.tsx(1 hunks)Frontend/src/components/collaboration-hub/activeCollabsMockData.ts(1 hunks)Frontend/src/components/collaboration-hub/mockProfileData.ts(1 hunks)Frontend/src/components/dashboard/creator-collaborations.tsx(1 hunks)Frontend/src/pages/Collaborations.tsx(2 hunks)Frontend/src/pages/DashboardPage.tsx(3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
Frontend/src/pages/DashboardPage.tsx (1)
Frontend/src/pages/Collaborations.tsx (1)
CollaborationsPage(18-186)
Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (1)
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (2)
CreatorMatchCardProps(8-19)CreatorMatchCard(34-114)
Frontend/src/components/collaboration-hub/ActiveCollabsGrid.tsx (1)
Frontend/src/components/collaboration-hub/activeCollabsMockData.ts (1)
activeCollabsMock(3-52)
Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx (2)
Frontend/src/components/ui/avatar.tsx (3)
Avatar(50-50)AvatarImage(50-50)AvatarFallback(50-50)Frontend/src/components/ui/button.tsx (1)
Button(54-54)
Frontend/src/components/collaboration-hub/ConnectModal.tsx (4)
Frontend/src/components/collaboration-hub/mockProfileData.ts (4)
mockWhyMatch(46-59)mockRequestTexts(40-44)mockProfileDetails(3-19)mockCollabIdeas(21-38)Frontend/src/components/ui/badge.tsx (1)
Badge(36-36)Frontend/src/components/ui/avatar.tsx (3)
Avatar(50-50)AvatarImage(50-50)AvatarFallback(50-50)Frontend/src/components/ui/button.tsx (1)
Button(54-54)
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (3)
Frontend/src/components/ui/badge.tsx (1)
Badge(36-36)Frontend/src/components/ui/avatar.tsx (3)
Avatar(50-50)AvatarImage(50-50)AvatarFallback(50-50)Frontend/src/components/ui/button.tsx (1)
Button(54-54)
🔇 Additional comments (14)
Frontend/src/components/collaboration-hub/mockProfileData.ts (1)
1-59: Comprehensive and well-structured mock data module.This mock data module provides excellent variety and realistic content for testing the collaboration hub features. The structure is well-organized with clear separation of concerns across different data types.
Strong data modeling for social links.
The social links structure with platform, URL, and icon fields is well-designed and extensible for future social media platforms.
Frontend/src/pages/DashboardPage.tsx (3)
25-25: Clean import integration.The import statement is correctly placed and follows the existing import pattern.
193-193: Good integration in overview tab.Replacing the placeholder with the actual CollaborationsPage component provides immediate value to users viewing the dashboard overview.
215-215: Proper header management in dedicated tab.Using
showHeader={false}appropriately prevents duplicate headers when the CollaborationsPage is rendered within the dashboard's collaborations tab.Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx (2)
8-8: Good constant definition for page size.Defining PAGE_SIZE as a constant makes it easy to modify pagination behavior in one place.
10-16: Solid pagination logic implementation.The pagination calculations correctly handle page boundaries and slice operations. The logic is clean and easy to understand.
Frontend/src/components/collaboration-hub/ActiveCollabsGrid.tsx (1)
1-7: LGTM! Clean imports and well-defined constants.The component follows good practices with clear separation of concerns. The constants for filtering and sorting options are appropriately defined and reusable.
Frontend/src/pages/Collaborations.tsx (2)
18-18: LGTM! Good addition of optional prop with default value.The
showHeaderprop with a default value oftrueis well-implemented, maintaining backward compatibility while adding flexibility.
167-167: CreatorMatchGrid correctly supportsmockCreatorMatchesAll required fields in
CreatorMatchCardProps(name, avatar, contentType, matchPercentage, audienceMatch, followers, engagement, content, collabs, whyMatch) are present in each mock object. The extraidfield is harmlessly ignored by the card component when spreading props, so no mismatches occur.No changes required.
Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx (1)
5-19: LGTM! Well-defined TypeScript interface.The interface clearly defines all required props with appropriate types. The structure is logical and comprehensive.
Frontend/src/components/collaboration-hub/ViewProfileModal.tsx (1)
7-20: LGTM! Well-structured TypeScript interfaces.The interfaces are clearly defined with appropriate optional properties and good naming conventions.
Frontend/src/components/collaboration-hub/ConnectModal.tsx (1)
7-22: LGTM! Clean interface definitions and constants.The interfaces are well-structured with appropriate prop types. The constants are clearly defined and meaningful.
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (2)
1-6: LGTM! Clean import organization.The imports are well-organized and all necessary dependencies are properly imported.
99-112: LGTM! Clean modal integration.The modal integration is well-implemented with proper state management and user experience considerations (transition delay between modals).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (1)
Frontend/src/pages/CollaborationDetails.tsx (1)
241-261: Consider moving detailed implementation notes to documentationThis extensive comment block about AI message enhancement would be better placed in project documentation or a separate design document rather than in the component code.
Would you like me to help create a separate documentation file for these AI feature specifications?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
Frontend/src/App.tsx(2 hunks)Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx(1 hunks)Frontend/src/pages/CollaborationDetails.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
Frontend/src/App.tsx (1)
Frontend/src/pages/CollaborationDetails.tsx (1)
CollaborationDetails(157-1259)
🔇 Additional comments (1)
Frontend/src/App.tsx (1)
7-7: LGTM!The import and route addition are correctly implemented following the existing patterns in the application.
Also applies to: 104-111
| export default function CollaborationDetails() { | ||
| const { id } = useParams<{ id: string }>(); | ||
| const navigate = useNavigate(); | ||
| const [newMessage, setNewMessage] = useState(""); | ||
| const [activeTab, setActiveTab] = useState("overview"); | ||
| const [showContractModal, setShowContractModal] = useState(false); | ||
| const [messageStyle, setMessageStyle] = useState("professional"); | ||
| const [showStyleOptions, setShowStyleOptions] = useState(false); | ||
| const [customStyle, setCustomStyle] = useState(""); | ||
| const [isEditingUpdate, setIsEditingUpdate] = useState(false); | ||
| const [editedUpdate, setEditedUpdate] = useState(""); | ||
| const [showDeliverableModal, setShowDeliverableModal] = useState(false); | ||
| const [selectedDeliverable, setSelectedDeliverable] = useState<Deliverable | null>(null); | ||
|
|
||
| // Find the collaboration data | ||
| const collaboration = activeCollabsMock.find(collab => collab.id === parseInt(id || "1")); | ||
|
|
||
| if (!collaboration) { | ||
| return ( | ||
| <div className="min-h-screen flex items-center justify-center"> | ||
| <div className="text-center"> | ||
| <h2 className="text-2xl font-bold text-gray-900 mb-2">Collaboration Not Found</h2> | ||
| <p className="text-gray-600 mb-4">The collaboration you're looking for doesn't exist.</p> | ||
| <Button onClick={() => navigate("/dashboard/collaborations")}> | ||
| Back to Collaborations | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const getStatusColor = (status: string) => { | ||
| switch (status) { | ||
| case "In Progress": return "bg-blue-100 text-blue-700"; | ||
| case "Awaiting Response": return "bg-yellow-100 text-yellow-700"; | ||
| case "Completed": return "bg-green-100 text-green-700"; | ||
| default: return "bg-gray-100 text-gray-700"; | ||
| } | ||
| }; | ||
|
|
||
| const getDeliverableStatusColor = (status: string) => { | ||
| switch (status) { | ||
| case "completed": return "bg-green-100 text-green-700"; | ||
| case "in-progress": return "bg-blue-100 text-blue-700"; | ||
| case "review": return "bg-yellow-100 text-yellow-700"; | ||
| case "pending": return "bg-gray-100 text-gray-700"; | ||
| default: return "bg-gray-100 text-gray-700"; | ||
| } | ||
| }; | ||
|
|
||
| const getMilestoneStatusColor = (status: string) => { | ||
| switch (status) { | ||
| case "completed": return "bg-green-100 text-green-700"; | ||
| case "in-progress": return "bg-blue-100 text-blue-700"; | ||
| case "upcoming": return "bg-gray-100 text-gray-700"; | ||
| default: return "bg-gray-100 text-gray-700"; | ||
| } | ||
| }; | ||
|
|
||
| const handleSendMessage = () => { | ||
| if (newMessage.trim()) { | ||
| // In a real app, this would send the message to the backend | ||
| console.log("Sending message:", newMessage); | ||
| setNewMessage(""); | ||
| } | ||
| }; | ||
|
|
||
| const handleViewContract = () => { | ||
| setShowContractModal(true); | ||
| }; | ||
|
|
||
| // Mock contract URL - in a real app, this would come from the collaboration data | ||
| const contractUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"; | ||
|
|
||
| // Message style options for AI enhancement | ||
| const messageStyles = [ | ||
| { value: "professional", label: "Professional", description: "Formal and business-like" }, | ||
| { value: "casual", label: "Casual", description: "Friendly and relaxed" }, | ||
| { value: "polite", label: "Polite", description: "Courteous and respectful" }, | ||
| { value: "concise", label: "Concise", description: "Brief and to the point" }, | ||
| { value: "enthusiastic", label: "Enthusiastic", description: "Energetic and positive" }, | ||
| { value: "constructive", label: "Constructive", description: "Helpful and solution-focused" } | ||
| ]; | ||
|
|
||
| /** | ||
| * AI Message Style Enhancement Feature(Not implemented yet) | ||
| * i am putting this here for future reference for contributors... | ||
| * This feature allows users to enhance their message content using AI to match different communication styles. | ||
| * | ||
| * Requirements for future development: | ||
| * 1. Integration with LLM API (OpenAI, Anthropic, etc.) to generate styled messages | ||
| * 2. Real-time message transformation as user types or selects style | ||
| * 3. Support for custom style descriptions (user-defined tone/approach) | ||
| * 4. Context awareness - consider collaboration history and relationship | ||
| * 5. Style suggestions based on message content and collaboration stage | ||
| * 6. Option to preview changes before applying | ||
| * 7. Learning from user preferences and successful communication patterns | ||
| * 8. Integration with collaboration analytics to suggest optimal communication timing | ||
| * | ||
| * Technical considerations: | ||
| * - API rate limiting and error handling | ||
| * - Caching of common style transformations | ||
| * - Privacy and data security for message content | ||
| * - Real-time collaboration features (typing indicators, etc.) | ||
| */ | ||
| const handleStyleChange = (style: string) => { | ||
| setMessageStyle(style); | ||
| setShowStyleOptions(false); | ||
|
|
||
| // TODO: Implement AI message transformation | ||
| // This would call an LLM API to transform the current message | ||
| // Example API call structure: | ||
| // const transformedMessage = await transformMessageStyle(newMessage, style); | ||
| // setNewMessage(transformedMessage); | ||
|
|
||
| console.log(`Applying ${style} style to message:`, newMessage); | ||
| }; | ||
|
|
||
| const handleCustomStyle = () => { | ||
| if (customStyle.trim()) { | ||
| // TODO: Implement custom style transformation | ||
| // This would use the custom style description to guide the AI transformation | ||
| console.log(`Applying custom style "${customStyle}" to message:`, newMessage); | ||
| setCustomStyle(""); | ||
| setShowStyleOptions(false); | ||
| } | ||
| }; | ||
|
|
||
| const handleEditUpdate = () => { | ||
| setEditedUpdate(collaboration.latestUpdate); | ||
| setIsEditingUpdate(true); | ||
| }; | ||
|
|
||
| const handleSaveUpdate = () => { | ||
| // TODO: Implement API call to save the updated latest update | ||
| // This would update the collaboration's latest update in the backend | ||
| console.log("Saving updated latest update:", editedUpdate); | ||
|
|
||
| // For now, we'll just close the edit mode | ||
| // In a real app, you would update the collaboration object here | ||
| setIsEditingUpdate(false); | ||
| }; | ||
|
|
||
| const handleCancelEdit = () => { | ||
| setIsEditingUpdate(false); | ||
| setEditedUpdate(""); | ||
| }; | ||
|
|
||
| const handleViewDeliverable = (deliverable: Deliverable) => { | ||
| setSelectedDeliverable(deliverable); | ||
| setShowDeliverableModal(true); | ||
| }; | ||
|
|
||
| const handleCloseDeliverableModal = () => { | ||
| setShowDeliverableModal(false); | ||
| setSelectedDeliverable(null); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex min-h-screen flex-col bg-white text-gray-900"> | ||
| {/* Main Header */} | ||
| <header className="sticky top-0 z-50 w-full border-b border-gray-200 bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/60"> | ||
| <div className="container flex h-16 items-center"> | ||
| <Link to="/" className="flex items-center space-x-2 mr-6 ml-6"> | ||
| <Rocket className="h-6 w-6 text-[hsl(262.1,83.3%,57.8%)]" /> | ||
| <span className="font-bold text-xl hidden md:inline-block">Inpact</span> | ||
| </Link> | ||
| <div className="flex items-center space-x-4"> | ||
| {[ | ||
| { to: "/dashboard", icon: LayoutDashboard, label: "Dashboard" }, | ||
| { to: "/dashboard/sponsorships", icon: Briefcase, label: "Sponsorships" }, | ||
| { to: "/dashboard/collaborations", icon: Users, label: "Collaborations" }, | ||
| { to: "/dashboard/contracts", icon: FileText, label: "Contracts" }, | ||
| { to: "/dashboard/analytics", icon: BarChart3, label: "Analytics" }, | ||
| { to: "/dashboard/messages", icon: MessageSquare, label: "Messages" }, | ||
| ].map(({ to, icon: Icon, label }) => ( | ||
| <Button | ||
| key={to} | ||
| variant="ghost" | ||
| size="sm" | ||
| className="w-9 px-0 hover:bg-[hsl(210,40%,96.1%)] hover:text-[hsl(222.2,47.4%,11.2%)]" | ||
| asChild | ||
| > | ||
| <Link to={to}> | ||
| <Icon className="h-5 w-5" /> | ||
| <span className="sr-only">{label}</span> | ||
| </Link> | ||
| </Button> | ||
| ))} | ||
| </div> | ||
| <div className="ml-auto flex items-center space-x-4"> | ||
| <div className="relative hidden md:flex"> | ||
| <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-[hsl(215.4,16.3%,46.9%)]" /> | ||
| <Input | ||
| type="search" | ||
| placeholder="Search..." | ||
| className="w-[200px] pl-8 md:w-[300px] rounded-full bg-[hsl(210,40%,96.1%)] border-[hsl(214.3,31.8%,91.4%)]" | ||
| /> | ||
| </div> | ||
| <ModeToggle /> | ||
| <UserNav /> | ||
| </div> | ||
| </div> | ||
| </header> | ||
|
|
||
| {/* Page Header */} | ||
| <div className="bg-white border-b border-gray-200"> | ||
| <div className="container mx-auto px-4 py-4"> | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center gap-4"> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={() => navigate("/dashboard/collaborations")} | ||
| className="flex items-center gap-2" | ||
| > | ||
| <ArrowLeft className="h-4 w-4" /> | ||
| Back to Collaborations | ||
| </Button> | ||
| <div> | ||
| <h1 className="text-2xl font-bold text-gray-900">{collaboration.collabTitle}</h1> | ||
| <p className="text-gray-600">Collaboration with {collaboration.collaborator.name}</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-3"> | ||
| <Badge className={getStatusColor(collaboration.status)}> | ||
| {collaboration.status} | ||
| </Badge> | ||
| <Button variant="outline" size="sm"> | ||
| <MoreHorizontal className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <main className="flex-1 space-y-4 p-4 md:p-8 pt-6"> | ||
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | ||
| {/* Main Content */} | ||
| <div className="lg:col-span-2 space-y-6"> | ||
| <Tabs value={activeTab} onValueChange={setActiveTab}> | ||
| <TabsList className="grid w-full grid-cols-4"> | ||
| <TabsTrigger value="overview">Overview</TabsTrigger> | ||
| <TabsTrigger value="messages">Messages</TabsTrigger> | ||
| <TabsTrigger value="deliverables">Deliverables</TabsTrigger> | ||
| <TabsTrigger value="timeline">Timeline</TabsTrigger> | ||
| </TabsList> | ||
|
|
||
| {/* Overview Tab */} | ||
| <TabsContent value="overview" className="space-y-6"> | ||
| {/* Collaboration Summary */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <Users className="h-5 w-5" /> | ||
| Collaboration Summary | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Project Details</h4> | ||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Start Date:</span> | ||
| <span className="font-medium">{collaboration.startDate}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Due Date:</span> | ||
| <span className="font-medium">{collaboration.dueDate}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Content Type:</span> | ||
| <span className="font-medium">{collaboration.collaborator.contentType}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Progress</h4> | ||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Deliverables:</span> | ||
| <span className="font-medium">{collaboration.deliverables.completed}/{collaboration.deliverables.total}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Messages:</span> | ||
| <span className="font-medium">{collaboration.messages}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Last Activity:</span> | ||
| <span className="font-medium">{collaboration.lastActivity}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div> | ||
| <div className="flex items-center justify-between mb-2"> | ||
| <h4 className="font-semibold text-gray-900">Latest Update</h4> | ||
| {!isEditingUpdate && ( | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={handleEditUpdate} | ||
| className="h-6 px-2 text-xs" | ||
| > | ||
| <Edit className="h-3 w-3 mr-1" /> | ||
| Edit | ||
| </Button> | ||
| )} | ||
| </div> | ||
|
|
||
| {isEditingUpdate ? ( | ||
| <div className="space-y-2"> | ||
| <Textarea | ||
| value={editedUpdate} | ||
| onChange={(e) => setEditedUpdate(e.target.value)} | ||
| className="min-h-[80px]" | ||
| placeholder="Enter the latest update..." | ||
| /> | ||
| <div className="flex gap-2"> | ||
| <Button | ||
| size="sm" | ||
| onClick={handleSaveUpdate} | ||
| className="text-xs" | ||
| > | ||
| Save | ||
| </Button> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleCancelEdit} | ||
| className="text-xs" | ||
| > | ||
| Cancel | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ) : ( | ||
| <p className="text-gray-700 bg-gray-50 p-3 rounded-lg"> | ||
| {collaboration.latestUpdate} | ||
| </p> | ||
| )} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Progress Tracking */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <BarChart3 className="h-5 w-5" /> | ||
| Progress Tracking | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div> | ||
| <div className="flex justify-between text-sm mb-2"> | ||
| <span className="text-gray-600">Timeline Progress</span> | ||
| <span className="font-medium">65%</span> | ||
| </div> | ||
| <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div className="h-3 bg-blue-500 rounded-full" style={{ width: "65%" }} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <div className="flex justify-between text-sm mb-2"> | ||
| <span className="text-gray-600">Deliverables Progress</span> | ||
| <span className="font-medium">{Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100)}%</span> | ||
| </div> | ||
| <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div | ||
| className="h-3 bg-green-500 rounded-full" | ||
| style={{ width: `${(collaboration.deliverables.completed / collaboration.deliverables.total) * 100}%` }} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* AI Project Overview & Recommendations */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <BarChart3 className="h-5 w-5" /> | ||
| AI Project Overview & Recommendations | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Project Health Analysis</h4> | ||
| <div className="bg-blue-50 border border-blue-200 rounded-lg p-3"> | ||
| <p className="text-sm text-blue-800"> | ||
| This collaboration is progressing well with 65% timeline completion. | ||
| The content creation phase is active and on track. | ||
| Communication frequency is optimal for this stage of the project. | ||
| </p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Current Timeline Recommendations</h4> | ||
| <div className="space-y-2"> | ||
| <div className="flex items-start gap-2"> | ||
| <div className="w-2 h-2 bg-blue-500 rounded-full mt-2 flex-shrink-0"></div> | ||
| <p className="text-sm text-gray-700"> | ||
| <strong>Content Creation Phase:</strong> Consider scheduling a review meeting | ||
| within the next 2 days to ensure alignment on video direction and style. | ||
| </p> | ||
| </div> | ||
| <div className="flex items-start gap-2"> | ||
| <div className="w-2 h-2 bg-green-500 rounded-full mt-2 flex-shrink-0"></div> | ||
| <p className="text-sm text-gray-700"> | ||
| <strong>Quality Check:</strong> Request a preview of the thumbnail design | ||
| to provide early feedback and avoid last-minute revisions. | ||
| </p> | ||
| </div> | ||
| <div className="flex items-start gap-2"> | ||
| <div className="w-2 h-2 bg-yellow-500 rounded-full mt-2 flex-shrink-0"></div> | ||
| <p className="text-sm text-gray-700"> | ||
| <strong>Risk Mitigation:</strong> Prepare backup content ideas in case | ||
| the current direction needs adjustment. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Communication Tips</h4> | ||
| <div className="bg-green-50 border border-green-200 rounded-lg p-3"> | ||
| <p className="text-sm text-green-800"> | ||
| <strong>Pro Tip:</strong> Use specific feedback when reviewing content. | ||
| Instead of "make it better," try "increase the energy in the first 30 seconds" | ||
| or "add more close-up shots of the product features." | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </TabsContent> | ||
|
|
||
| {/* Messages Tab */} | ||
| <TabsContent value="messages" className="space-y-4"> | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <MessageSquare className="h-5 w-5" /> | ||
| Messages | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-4 mb-4"> | ||
| {mockMessages.map((message) => ( | ||
| <div key={message.id} className={`flex ${message.isOwn ? 'justify-end' : 'justify-start'}`}> | ||
| <div className={`max-w-xs lg:max-w-md ${message.isOwn ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-900'} rounded-lg p-3`}> | ||
| <div className="text-sm font-medium mb-1">{message.sender}</div> | ||
| <div className="text-sm">{message.content}</div> | ||
| <div className={`text-xs mt-2 ${message.isOwn ? 'text-blue-100' : 'text-gray-500'}`}> | ||
| {message.timestamp} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
|
|
||
| <Separator className="my-4" /> | ||
|
|
||
| {/* AI Message Style Enhancement */} | ||
| <div className="mb-4"> | ||
| <div className="flex items-center justify-between mb-2"> | ||
| <h4 className="text-sm font-medium text-gray-900">Message Style</h4> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={() => setShowStyleOptions(!showStyleOptions)} | ||
| className="text-xs" | ||
| > | ||
| {messageStyles.find(s => s.value === messageStyle)?.label || "Professional"} | ||
| </Button> | ||
| </div> | ||
|
|
||
| {showStyleOptions && ( | ||
| <div className="bg-gray-50 border border-gray-200 rounded-lg p-3 mb-3"> | ||
| <div className="grid grid-cols-2 gap-2 mb-3"> | ||
| {messageStyles.map((style) => ( | ||
| <Button | ||
| key={style.value} | ||
| variant={messageStyle === style.value ? "default" : "outline"} | ||
| size="sm" | ||
| onClick={() => handleStyleChange(style.value)} | ||
| className="text-xs justify-start" | ||
| > | ||
| {style.label} | ||
| </Button> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className="flex gap-2"> | ||
| <Input | ||
| placeholder="Enter custom style (e.g., 'encouraging', 'direct')" | ||
| value={customStyle} | ||
| onChange={(e) => setCustomStyle(e.target.value)} | ||
| className="flex-1 text-xs" | ||
| onKeyPress={(e) => e.key === 'Enter' && handleCustomStyle()} | ||
| /> | ||
| <Button size="sm" onClick={handleCustomStyle} className="text-xs"> | ||
| Apply | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
| <div className="flex gap-2"> | ||
| <Textarea | ||
| placeholder="Type your message..." | ||
| value={newMessage} | ||
| onChange={(e) => setNewMessage(e.target.value)} | ||
| className="flex-1" | ||
| rows={2} | ||
| /> | ||
| <Button onClick={handleSendMessage} className="px-4"> | ||
| <Send className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </TabsContent> | ||
|
|
||
| {/* Deliverables Tab */} | ||
| <TabsContent value="deliverables" className="space-y-4"> | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <FileText className="h-5 w-5" /> | ||
| Deliverables | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-4"> | ||
| {mockDeliverables.map((deliverable) => ( | ||
| <div key={deliverable.id} className="border border-gray-200 rounded-lg p-4"> | ||
| <div className="flex items-start justify-between mb-3"> | ||
| <div className="flex-1"> | ||
| <h4 className="font-semibold text-gray-900">{deliverable.title}</h4> | ||
| <p className="text-sm text-gray-600 mt-1">{deliverable.description}</p> | ||
| </div> | ||
| <Badge className={getDeliverableStatusColor(deliverable.status)}> | ||
| {deliverable.status.replace('-', ' ')} | ||
| </Badge> | ||
| </div> | ||
|
|
||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm"> | ||
| <div> | ||
| <span className="text-gray-600">Due Date:</span> | ||
| <span className="ml-2 font-medium">{deliverable.dueDate}</span> | ||
| </div> | ||
| <div> | ||
| <span className="text-gray-600">Assigned To:</span> | ||
| <span className="ml-2 font-medium">{deliverable.assignedTo}</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| {deliverable.files && deliverable.files.length > 0 && ( | ||
| <div className="mt-3"> | ||
| <span className="text-sm text-gray-600">Files:</span> | ||
| <div className="flex gap-2 mt-1"> | ||
| {deliverable.files.map((file, index) => ( | ||
| <Button key={index} variant="outline" size="sm" className="text-xs"> | ||
| <Download className="h-3 w-3 mr-1" /> | ||
| {file} | ||
| </Button> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="flex gap-2 mt-4"> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={() => handleViewDeliverable(deliverable)} | ||
| > | ||
| <Eye className="h-4 w-4 mr-1" /> | ||
| View | ||
| </Button> | ||
| <Button variant="outline" size="sm"> | ||
| <Edit className="h-4 w-4 mr-1" /> | ||
| Edit | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </TabsContent> | ||
|
|
||
| {/* Timeline Tab */} | ||
| <TabsContent value="timeline" className="space-y-4"> | ||
| <Card> | ||
| <CardHeader> | ||
| <div className="flex items-center justify-between"> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <Calendar className="h-5 w-5" /> | ||
| Project Timeline | ||
| </CardTitle> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleViewContract} | ||
| className="flex items-center gap-2" | ||
| > | ||
| <FileText className="h-4 w-4" /> | ||
| View Contract | ||
| </Button> | ||
| </div> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-6"> | ||
| {mockMilestones.map((milestone, index) => ( | ||
| <div key={milestone.id} className="flex gap-4"> | ||
| <div className="flex flex-col items-center"> | ||
| <div className={`w-4 h-4 rounded-full ${ | ||
| milestone.status === 'completed' ? 'bg-green-500' : | ||
| milestone.status === 'in-progress' ? 'bg-blue-500' : 'bg-gray-300' | ||
| }`} /> | ||
| {index < mockMilestones.length - 1 && ( | ||
| <div className="w-0.5 h-12 bg-gray-200 mt-2" /> | ||
| )} | ||
| </div> | ||
| <div className="flex-1"> | ||
| <div className="flex items-center justify-between mb-2"> | ||
| <h4 className="font-semibold text-gray-900">{milestone.title}</h4> | ||
| <Badge className={getMilestoneStatusColor(milestone.status)}> | ||
| {milestone.status.replace('-', ' ')} | ||
| </Badge> | ||
| </div> | ||
| <p className="text-sm text-gray-600 mb-2">{milestone.description}</p> | ||
| <div className="flex items-center justify-between text-sm"> | ||
| <span className="text-gray-500">Due: {milestone.dueDate}</span> | ||
| {milestone.status === 'in-progress' && ( | ||
| <span className="text-blue-600 font-medium">{milestone.progress}% complete</span> | ||
| )} | ||
| </div> | ||
| {milestone.status === 'in-progress' && ( | ||
| <div className="w-full h-2 bg-gray-200 rounded-full mt-2 overflow-hidden"> | ||
| <div | ||
| className="h-2 bg-blue-500 rounded-full" | ||
| style={{ width: `${milestone.progress}%` }} | ||
| /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </TabsContent> | ||
| </Tabs> | ||
| </div> | ||
|
|
||
| {/* Sidebar */} | ||
| <div className="space-y-6"> | ||
| {/* Collaborator Profile */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Collaborator</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="flex items-center gap-3"> | ||
| <Avatar className="h-12 w-12"> | ||
| <AvatarImage src={collaboration.collaborator.avatar} alt={collaboration.collaborator.name} /> | ||
| <AvatarFallback className="bg-gray-200"> | ||
| {collaboration.collaborator.name.slice(0, 2).toUpperCase()} | ||
| </AvatarFallback> | ||
| </Avatar> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900">{collaboration.collaborator.name}</h4> | ||
| <p className="text-sm text-gray-600">{collaboration.collaborator.contentType}</p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex items-center gap-2 text-gray-600"> | ||
| <Star className="h-4 w-4" /> | ||
| <span>4.8/5 rating</span> | ||
| </div> | ||
| <div className="flex items-center gap-2 text-gray-600"> | ||
| <TrendingUp className="h-4 w-4" /> | ||
| <span>500K+ followers</span> | ||
| </div> | ||
| <div className="flex items-center gap-2 text-gray-600"> | ||
| <Activity className="h-4 w-4" /> | ||
| <span>95% completion rate</span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Button className="w-full" size="sm"> | ||
| <MessageSquare className="h-4 w-4 mr-2" /> | ||
| Send Message | ||
| </Button> | ||
| <Button variant="outline" className="w-full" size="sm"> | ||
| <Mail className="h-4 w-4 mr-2" /> | ||
| </Button> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Quick Actions */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Quick Actions</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-2"> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Edit className="h-4 w-4 mr-2" /> | ||
| Edit Collaboration | ||
| </Button> | ||
| <Button | ||
| variant="outline" | ||
| className="w-full justify-start" | ||
| size="sm" | ||
| onClick={handleViewContract} | ||
| > | ||
| <FileText className="h-4 w-4 mr-2" /> | ||
| View Contract | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Download className="h-4 w-4 mr-2" /> | ||
| Export Details | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <ExternalLink className="h-4 w-4 mr-2" /> | ||
| View Profile | ||
| </Button> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Project Stats */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Project Stats</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-2 gap-4 text-center"> | ||
| <div> | ||
| <div className="text-2xl font-bold text-blue-600">65%</div> | ||
| <div className="text-xs text-gray-600">Timeline</div> | ||
| </div> | ||
| <div> | ||
| <div className="text-2xl font-bold text-green-600"> | ||
| {Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100)}% | ||
| </div> | ||
| <div className="text-xs text-gray-600">Deliverables</div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Days Remaining:</span> | ||
| <span className="font-medium">3 days</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Files Shared:</span> | ||
| <span className="font-medium">5</span> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| </div> | ||
| </main> | ||
|
|
||
| {/* Deliverable View Modal */} | ||
| {showDeliverableModal && selectedDeliverable && ( | ||
| <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"> | ||
| <div className="bg-white rounded-lg w-full max-w-4xl h-[90vh] flex flex-col"> | ||
| <div className="flex items-center justify-between p-4 border-b border-gray-200"> | ||
| <h3 className="text-2xl font-bold text-gray-900">Deliverable Details</h3> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={handleCloseDeliverableModal} | ||
| className="h-8 w-8 p-0" | ||
| > | ||
| <X className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| <div className="flex-1 overflow-y-auto p-4"> | ||
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | ||
| {/* Main Content */} | ||
| <div className="lg:col-span-2 space-y-6"> | ||
| {/* Deliverable Details */} | ||
| <Card> | ||
| <CardHeader> | ||
| <div className="mb-1"> | ||
| <span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Deliverable Details</span> | ||
| </div> | ||
| <CardTitle className="text-2xl font-bold text-gray-900 mb-1"> | ||
| {selectedDeliverable.title} | ||
| </CardTitle> | ||
| <p className="text-sm text-gray-600 mt-1">{selectedDeliverable.description}</p> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Status & Progress</h4> | ||
| <div className="space-y-2"> | ||
| <div className="flex items-center gap-2"> | ||
| <Badge className={getDeliverableStatusColor(selectedDeliverable.status)}> | ||
| {selectedDeliverable.status.replace('-', ' ')} | ||
| </Badge> | ||
| <span className="text-sm text-gray-600"> | ||
| {selectedDeliverable.status === 'completed' ? '100%' : | ||
| selectedDeliverable.status === 'in-progress' ? '65%' : '0%'} complete | ||
| </span> | ||
| </div> | ||
| {selectedDeliverable.status === 'in-progress' && ( | ||
| <div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div className="h-2 bg-blue-500 rounded-full" style={{ width: "65%" }} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Timeline</h4> | ||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Due Date:</span> | ||
| <span className="font-medium">{selectedDeliverable.dueDate}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Assigned To:</span> | ||
| <span className="font-medium">{selectedDeliverable.assignedTo}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Created:</span> | ||
| <span className="font-medium">{collaboration.startDate}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Files & Attachments */} | ||
| {selectedDeliverable.files && selectedDeliverable.files.length > 0 && ( | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <Download className="h-5 w-5" /> | ||
| Files & Attachments | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-3"> | ||
| {selectedDeliverable.files.map((file, index) => ( | ||
| <div key={index} className="flex items-center justify-between p-3 border border-gray-200 rounded-lg"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center"> | ||
| <FileText className="h-5 w-5 text-gray-600" /> | ||
| </div> | ||
| <div> | ||
| <p className="font-medium text-gray-900">{file}</p> | ||
| <p className="text-sm text-gray-600">Uploaded 2 days ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Button variant="outline" size="sm"> | ||
| <Eye className="h-4 w-4 mr-1" /> | ||
| Preview | ||
| </Button> | ||
| <Button variant="outline" size="sm"> | ||
| <Download className="h-4 w-4 mr-1" /> | ||
| Download | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| )} | ||
|
|
||
| {/* Comments & Feedback */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <MessageSquare className="h-5 w-5" /> | ||
| Comments & Feedback | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-4"> | ||
| <div className="bg-gray-50 p-3 rounded-lg"> | ||
| <div className="flex items-center gap-2 mb-2"> | ||
| <Avatar className="h-6 w-6"> | ||
| <AvatarImage src={collaboration.collaborator.avatar} alt={collaboration.collaborator.name} /> | ||
| <AvatarFallback className="text-xs">{collaboration.collaborator.name.slice(0, 2).toUpperCase()}</AvatarFallback> | ||
| </Avatar> | ||
| <span className="text-sm font-medium">{collaboration.collaborator.name}</span> | ||
| <span className="text-xs text-gray-500">2 days ago</span> | ||
| </div> | ||
| <p className="text-sm text-gray-700"> | ||
| "The first draft is ready for review. I've included the main product features | ||
| and added some B-roll footage. Let me know if you'd like any adjustments to the pacing." | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="bg-blue-50 p-3 rounded-lg"> | ||
| <div className="flex items-center gap-2 mb-2"> | ||
| <Avatar className="h-6 w-6"> | ||
| <AvatarFallback className="text-xs bg-blue-100">You</AvatarFallback> | ||
| </Avatar> | ||
| <span className="text-sm font-medium">You</span> | ||
| <span className="text-xs text-gray-500">1 day ago</span> | ||
| </div> | ||
| <p className="text-sm text-gray-700"> | ||
| "Great work! The pacing looks good. Could you add a few more close-up shots | ||
| of the product features around the 1:30 mark?" | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="flex gap-2 mt-4"> | ||
| <Textarea | ||
| placeholder="Add a comment or feedback..." | ||
| className="flex-1" | ||
| rows={2} | ||
| /> | ||
| <Button size="sm"> | ||
| <Send className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
|
|
||
| {/* Sidebar */} | ||
| <div className="space-y-6"> | ||
| {/* Quick Actions */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Quick Actions</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-2"> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Edit className="h-4 w-4 mr-2" /> | ||
| Edit Deliverable | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Download className="h-4 w-4 mr-2" /> | ||
| Download All Files | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <MessageSquare className="h-4 w-4 mr-2" /> | ||
| Send Message | ||
| </Button> | ||
| {selectedDeliverable.status !== 'completed' && ( | ||
| <Button className="w-full justify-start" size="sm"> | ||
| <CheckCircle className="h-4 w-4 mr-2" /> | ||
| Mark Complete | ||
| </Button> | ||
| )} | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Deliverable Stats */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Deliverable Stats</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-2 gap-4 text-center"> | ||
| <div> | ||
| <div className="text-2xl font-bold text-blue-600"> | ||
| {selectedDeliverable.status === 'completed' ? '100%' : | ||
| selectedDeliverable.status === 'in-progress' ? '65%' : '0%'} | ||
| </div> | ||
| <div className="text-xs text-gray-600">Progress</div> | ||
| </div> | ||
| <div> | ||
| <div className="text-2xl font-bold text-green-600"> | ||
| {selectedDeliverable.files?.length || 0} | ||
| </div> | ||
| <div className="text-xs text-gray-600">Files</div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Days Remaining:</span> | ||
| <span className="font-medium">3 days</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Comments:</span> | ||
| <span className="font-medium">2</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Last Updated:</span> | ||
| <span className="font-medium">1 day ago</span> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Version History */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Version History</CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-3"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-green-500 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.2 - Final Draft</p> | ||
| <p className="text-xs text-gray-600">Updated 1 day ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-blue-500 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.1 - First Review</p> | ||
| <p className="text-xs text-gray-600">Updated 2 days ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-gray-300 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.0 - Initial Draft</p> | ||
| <p className="text-xs text-gray-600">Created 3 days ago</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Contract Modal */} | ||
| {showContractModal && ( | ||
| <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"> | ||
| <div className="bg-white rounded-lg w-full max-w-6xl h-[90vh] flex flex-col"> | ||
| <div className="flex items-center justify-between p-4 border-b border-gray-200"> | ||
| <h3 className="text-lg font-semibold text-gray-900"> | ||
| Collaboration Contract - {collaboration.collabTitle} | ||
| </h3> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={() => setShowContractModal(false)} | ||
| className="h-8 w-8 p-0" | ||
| > | ||
| <X className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| <div className="flex-1 p-4"> | ||
| <iframe | ||
| src={contractUrl} | ||
| className="w-full h-full border border-gray-200 rounded" | ||
| title="Collaboration Contract" | ||
| /> | ||
| </div> | ||
| <div className="flex items-center justify-between p-4 border-t border-gray-200"> | ||
| <div className="text-sm text-gray-600"> | ||
| Contract uploaded on {collaboration.startDate} | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Button variant="outline" size="sm"> | ||
| <Download className="h-4 w-4 mr-2" /> | ||
| Download | ||
| </Button> | ||
| <Button size="sm"> | ||
| <FileText className="h-4 w-4 mr-2" /> | ||
| Sign Contract | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider breaking down this large component
This 1259-line component handles too many responsibilities. Consider extracting into smaller, focused components:
Suggested component breakdown:
CollaborationOverview- Overview tab content (lines 406-599)CollaborationMessages- Messages tab with AI styling (lines 602-687)CollaborationDeliverables- Deliverables list and management (lines 690-756)CollaborationTimeline- Timeline and milestones (lines 759-820)CollaboratorSidebar- Collaborator profile and stats (lines 824-938)DeliverableModal- Deliverable details modal (lines 942-1213)ContractModal- Contract viewing modal (lines 1216-1256)
This would improve maintainability, enable better testing, and make the code more modular.
🤖 Prompt for AI Agents
In Frontend/src/pages/CollaborationDetails.tsx from lines 1 to 1259, the
component is excessively large and handles multiple responsibilities, making it
hard to maintain. Refactor by extracting the main tab contents and side
components into smaller, focused components as suggested: CollaborationOverview
(lines 406-599), CollaborationMessages (602-687), CollaborationDeliverables
(690-756), CollaborationTimeline (759-820), CollaboratorSidebar (824-938),
DeliverableModal (942-1213), and ContractModal (1216-1256). Replace the
corresponding JSX in the main component with these new components to improve
modularity and maintainability.
| const [selectedDeliverable, setSelectedDeliverable] = useState<Deliverable | null>(null); | ||
|
|
||
| // Find the collaboration data | ||
| const collaboration = activeCollabsMock.find(collab => collab.id === parseInt(id || "1")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid silent fallback to default ID
The fallback to "1" when id is undefined could mask routing issues. Consider handling the undefined case explicitly:
- const collaboration = activeCollabsMock.find(collab => collab.id === parseInt(id || "1"));
+ const collaboration = id ? activeCollabsMock.find(collab => collab.id === parseInt(id)) : undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const collaboration = activeCollabsMock.find(collab => collab.id === parseInt(id || "1")); | |
| const collaboration = id | |
| ? activeCollabsMock.find(collab => collab.id === parseInt(id)) | |
| : undefined; |
🤖 Prompt for AI Agents
In Frontend/src/pages/CollaborationDetails.tsx at line 172, avoid silently
defaulting to ID "1" when the id is undefined, as this can hide routing
problems. Instead, explicitly check if id is undefined before using it, and
handle that case appropriately, such as showing an error message or redirecting,
rather than falling back to a default ID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
Frontend/src/pages/Collaborations.tsx (1)
137-419: Consider implementing error boundaries and loading states.The component lacks error handling and loading states, which are essential for a production application dealing with async operations.
Add error boundaries and loading states:
const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const handleWithErrorHandling = async (operation: () => Promise<void>) => { try { setLoading(true); setError(null); await operation(); } catch (err) { setError(err.message); } finally { setLoading(false); } };This ensures better user experience when API calls fail or take time to complete.
🧹 Nitpick comments (1)
Frontend/src/components/collaboration-hub/CollabRequests.tsx (1)
97-205: Consider breaking down the component for better maintainability.This component is quite large and handles multiple responsibilities. Consider extracting sub-components for better code organization.
Extract components like:
CollabRequestCardfor individual request renderingAIInsightsSectionfor the advantages/ideas/recommendations sectionRequestActionsfor the action buttonsThis would improve readability and reusability while following the single responsibility principle.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Frontend/src/components/collaboration-hub/CollabRequests.tsx(1 hunks)Frontend/src/pages/Collaborations.tsx(2 hunks)
| const mockRequests = [ | ||
| { | ||
| id: 1, | ||
| sender: { | ||
| name: "TechSavvy", | ||
| avatar: "https://randomuser.me/api/portraits/men/32.jpg", | ||
| contentNiche: "Tech Reviews", | ||
| audienceSize: "250K", | ||
| followers: 250000, | ||
| engagement: "4.2%", | ||
| rating: 4.7, | ||
| }, | ||
| summary: "Collaboration for a new smartphone launch campaign.", | ||
| proposal: { | ||
| contentLength: "5-7 min video", | ||
| paymentSchedule: "50% upfront, 50% after delivery", | ||
| numberOfPosts: "2 Instagram posts, 1 YouTube video", | ||
| timeline: "Within 3 weeks of product launch", | ||
| notes: "Open to creative input and additional deliverables." | ||
| }, | ||
| stats: { | ||
| posts: 120, | ||
| completionRate: "98%", | ||
| avgViews: "80K" | ||
| }, | ||
| ai: { | ||
| advantages: [ | ||
| "Access to a highly engaged tech audience.", | ||
| "Boosts brand credibility through trusted reviews.", | ||
| "Potential for long-term partnership." | ||
| ], | ||
| ideas: [ | ||
| "Unboxing and first impressions video.", | ||
| "Live Q&A session with audience.", | ||
| "Social media giveaway collaboration." | ||
| ], | ||
| recommendations: [ | ||
| "Highlight unique features in the first 60 seconds.", | ||
| "Leverage Instagram Stories for behind-the-scenes content.", | ||
| "Schedule a follow-up review after 1 month." | ||
| ] | ||
| } | ||
| }, | ||
| { | ||
| id: 2, | ||
| sender: { | ||
| name: "EcoChic", | ||
| avatar: "https://randomuser.me/api/portraits/women/44.jpg", | ||
| contentNiche: "Sustainable Fashion", | ||
| audienceSize: "180K", | ||
| followers: 180000, | ||
| engagement: "5.1%", | ||
| rating: 4.9, | ||
| }, | ||
| summary: "Proposal for a sustainable clothing line promotion.", | ||
| proposal: { | ||
| contentLength: "3-5 min Instagram Reel", | ||
| paymentSchedule: "Full payment after campaign", | ||
| numberOfPosts: "1 Reel, 2 Stories", | ||
| timeline: "Next month", | ||
| notes: "Would love to brainstorm eco-friendly angles together." | ||
| }, | ||
| stats: { | ||
| posts: 95, | ||
| completionRate: "96%", | ||
| avgViews: "60K" | ||
| }, | ||
| ai: { | ||
| advantages: [ | ||
| "Tap into eco-conscious audience.", | ||
| "Enhance brand's sustainability image.", | ||
| "Opportunity for co-branded content." | ||
| ], | ||
| ideas: [ | ||
| "Instagram Reels styling challenge.", | ||
| "Joint blog post on sustainable fashion tips.", | ||
| "Giveaway of eco-friendly products." | ||
| ], | ||
| recommendations: [ | ||
| "Feature behind-the-scenes of production.", | ||
| "Encourage user-generated content with a hashtag.", | ||
| "Host a live styling session." | ||
| ] | ||
| } | ||
| } | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Extract mock data to a separate file for better maintainability.
The extensive mock data should be moved to a dedicated file like collabRequestsMockData.ts to improve component readability and reusability.
Apply this refactor:
-// Mock data for incoming requests
-const mockRequests = [
- {
- id: 1,
- sender: {
- // ... extensive mock data
- }
- }
-];Create a new file Frontend/src/components/collaboration-hub/collabRequestsMockData.ts:
export const mockRequests = [
// Move all mock data here
];Then import it:
+import { mockRequests } from './collabRequestsMockData';🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollabRequests.tsx lines 10 to 95,
the large mockRequests array should be moved to a new file for better
maintainability. Create a new file named collabRequestsMockData.ts in the same
directory, export the mockRequests array from there, and then import it back
into CollabRequests.tsx. This will keep the component file cleaner and improve
reusability.
|
@chandansgowda I have pushed all the frontend code and will now proceed creating commits for backed...I'll request a review for the work done till this stage..... |
…t(resolving coderabbit suggestions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
♻️ Duplicate comments (3)
Frontend/src/components/collaboration-hub/ConnectModal.tsx (1)
28-30: Handle empty and small idea sets in paginationThe current logic assumes
mockCollabIdeas.lengthis always ≥ IDEAS_PER_PAGE and non-zero. This leads to a divide-by-zero in the modulo and always renders a full page even when fewer ideas exist.Please update
Frontend/src/components/collaboration-hub/ConnectModal.tsxaround lines 26–30:const totalIdeas = mockCollabIdeas.length; + // Guard against zero ideas + if (totalIdeas === 0) { + // no ideas to show + const ideasToShow: typeof mockCollabIdeas = []; + } else { + // only show up to the available number when less than one page + const displayCount = Math.min(IDEAS_PER_PAGE, totalIdeas); + const startIdx = (ideasPage * IDEAS_PER_PAGE) % totalIdeas; - const ideasToShow = Array.from({ length: IDEAS_PER_PAGE }, (_, i) => mockCollabIdeas[(startIdx + i) % totalIdeas]); + const ideasToShow = Array.from( + { length: displayCount }, + (_, i) => mockCollabIdeas[(startIdx + i) % totalIdeas] + ); + }This ensures:
- No runtime error when
totalIdeas === 0- You only render as many items as exist when
totalIdeas < IDEAS_PER_PAGEFrontend/src/components/collaboration-hub/CollaborationOverviewTab.tsx (1)
119-119: Timeline progress is hardcodedBased on previous feedback, hardcoded progress values are acceptable in frontend-only commits as UI placeholders.
Frontend/src/pages/Collaborations.tsx (1)
232-243: Extract modal components to separate filesThe inline modal implementations make this component large and difficult to maintain, as mentioned in previous reviews.
🧹 Nitpick comments (15)
Frontend/src/components/collaboration-hub/CollaborationQuickActions.tsx (2)
6-6: Define a proper interface for component props.Consider defining a proper TypeScript interface instead of inline typing for better maintainability and reusability.
+interface CollaborationQuickActionsProps { + handleViewContract: () => void; +} + -export default function CollaborationQuickActions({ handleViewContract }: { handleViewContract: () => void }) { +export default function CollaborationQuickActions({ handleViewContract }: CollaborationQuickActionsProps) {
13-16: Missing click handlers for action buttons.Three buttons (Edit Collaboration, Export Details, View Profile) don't have click handlers implemented. Consider adding TODO comments or placeholders to track future implementation.
Would you like me to help implement placeholder handlers or create issues to track these missing features?
Also applies to: 26-33
Frontend/src/components/collaboration-hub/CollaboratorSidebar.tsx (1)
43-51: Missing event handlers for communication buttons.The "Send Message" and "Email" buttons don't have click handlers. Consider adding props for these handlers or TODO comments to track future implementation.
Would you like me to help define the handler props interface for these actions?
Frontend/src/components/collaboration-hub/CollaborationDeliverables.tsx (1)
64-67: Missing click handler for Edit button.The "Edit" button doesn't have a click handler implemented. Consider adding a prop for this handler or a TODO comment.
Would you like me to help add the missing
handleEditDeliverableprop to the interface?Frontend/src/components/collaboration-hub/CreatorSearchModal.tsx (2)
12-12: Improve type safety by defining proper interfaces.Using
anyfor the creator parameter reduces type safety and IDE support. Consider defining a properCreatorinterface.+interface Creator { + id: number; + name: string; + contentType: string; + location: string; + // Add other creator properties as needed +} interface CreatorSearchModalProps { open: boolean; onClose: () => void; - onConnect?: (creator: any) => void; + onConnect?: (creator: Creator) => void; }
17-17: Replaceany[]with properly typed array.The search results should use a specific type instead of
any[]for better type safety.- const [aiSearchResults, setAiSearchResults] = useState<any[]>([]); + const [aiSearchResults, setAiSearchResults] = useState<Creator[]>([]);Frontend/src/components/collaboration-hub/CollaborationTimelineTab.tsx (1)
13-17: Improve type safety for component props.Using
anytypes reduces type safety. Consider defining proper interfaces for the props.+interface Milestone { + id: string | number; + title: string; + description: string; + status: 'completed' | 'in-progress' | 'pending'; + dueDate: string; + progress?: number; +} +interface Collaboration { + // Define collaboration properties as needed +} export default function CollaborationTimelineTab({ mockMilestones, handleViewContract, contractUrl, collaboration, getMilestoneStatusColor }: { - mockMilestones: any[]; + mockMilestones: Milestone[]; handleViewContract: () => void; contractUrl: string; - collaboration: any; + collaboration: Collaboration; getMilestoneStatusColor: (status: string) => string; }) {Frontend/src/components/collaboration-hub/CollaborationMessagesTab.tsx (2)
23-24: Improve type safety for message and style arrays.Using
any[]reduces type safety. Define proper interfaces for messages and styles.+interface Message { + id: string | number; + sender: string; + content: string; + timestamp: string; + isOwn: boolean; +} +interface MessageStyle { + value: string; + label: string; +} export default function CollaborationMessagesTab({ mockMessages, messageStyles, // ... other props }: { - mockMessages: any[]; - messageStyles: any[]; + mockMessages: Message[]; + messageStyles: MessageStyle[]; // ... other prop types }) {
9-35: Consider breaking down this component into smaller, focused components.This component has 11+ props and handles multiple concerns (message display, style selection, message composition). Consider splitting it into smaller, more focused components.
Consider extracting:
MessageListcomponent for displaying messagesMessageStyleSelectorcomponent for style selectionMessageComposercomponent for composing new messagesThis would improve maintainability and make the components more reusable.
Frontend/src/components/collaboration-hub/NewCollaborationModal.tsx (1)
13-13: Improve type safety for better development experience.Using
anytypes reduces IDE support and type safety. Consider defining proper interfaces.+interface Creator { + id: number; + name: string; + contentType: string; + location: string; + // Add other properties as needed +} +interface CollaborationData { + creator: Creator; + description: string; + proposal: ProposalData; + reviewed: boolean; +} interface NewCollaborationModalProps { open: boolean; onClose: () => void; - onSubmit?: (data: any) => void; + onSubmit?: (data: CollaborationData) => void; } // In component: - const [selectedCreator, setSelectedCreator] = useState<any>(null); + const [selectedCreator, setSelectedCreator] = useState<Creator | null>(null);Also applies to: 27-27
Frontend/src/components/ui/dialog.tsx (1)
209-209: Remove unused refsThe refs created for DialogTitle and DialogDescription are never used.
- ref={React.useRef<HTMLHeadingElement>(null)}- ref={React.useRef<HTMLParagraphElement>(null)}Also applies to: 226-226
Frontend/src/pages/Collaborations.tsx (1)
37-40: Add TODO comments for placeholder handlersThe handler functions currently only log to console, indicating they're placeholders.
const handleNewCollabSubmit = (data: any) => { + // TODO: Implement backend integration for collaboration submission console.log("New collaboration request submitted:", data); - // Handle the submission logic here }; const handleCreatorConnect = (creator: any) => { + // TODO: Implement backend integration for creator connection console.log("Connecting with creator:", creator); - // Handle the connection logic here };Also applies to: 42-45
Frontend/src/components/collaboration-hub/CollaborationOverview.tsx (1)
127-131: Consider making timeline progress dynamicWhile hardcoded values are acceptable for frontend-only development, consider adding a prop for timeline progress to improve reusability.
+ timelineProgress?: number; - <span className="font-medium">65%</span> + <span className="font-medium">{timelineProgress || 65}%</span> - <div className="h-3 bg-blue-500 rounded-full" style={{ width: "65%" }} /> + <div className="h-3 bg-blue-500 rounded-full" style={{ width: `${timelineProgress || 65}%` }} />Frontend/src/pages/CollaborationDetails.tsx (2)
48-74: Consider extracting interfaces to a separate types fileThe interface definitions are well-structured but would benefit from being in a dedicated types file for better organization and reusability across components.
Consider creating
Frontend/src/types/collaboration.ts:export interface Message { id: number; sender: string; content: string; timestamp: string; isOwn: boolean; } export interface Deliverable { id: number; title: string; description: string; status: "pending" | "in-progress" | "completed" | "review"; dueDate: string; assignedTo: string; files?: string[]; } export interface Milestone { id: number; title: string; description: string; dueDate: string; status: "upcoming" | "in-progress" | "completed"; progress: number; }
318-360: Extract header navigation into a separate componentThe header navigation is repetitive and could be extracted into a reusable component for consistency across pages.
Consider creating
Frontend/src/components/layout/DashboardHeader.tsx:interface DashboardHeaderProps { currentPath?: string; } export default function DashboardHeader({ currentPath }: DashboardHeaderProps) { const navigationItems = [ { to: "/dashboard", icon: LayoutDashboard, label: "Dashboard" }, { to: "/dashboard/sponsorships", icon: Briefcase, label: "Sponsorships" }, { to: "/dashboard/collaborations", icon: Users, label: "Collaborations" }, { to: "/dashboard/contracts", icon: FileText, label: "Contracts" }, { to: "/dashboard/analytics", icon: BarChart3, label: "Analytics" }, { to: "/dashboard/messages", icon: MessageSquare, label: "Messages" }, ]; return ( <header className="sticky top-0 z-50 w-full border-b border-gray-200 bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/60"> {/* Header content */} </header> ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationDeliverables.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationMessages.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationMessagesTab.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationOverview.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationOverviewTab.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationProjectStats.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationQuickActions.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaborationTimelineTab.tsx(1 hunks)Frontend/src/components/collaboration-hub/CollaboratorSidebar.tsx(1 hunks)Frontend/src/components/collaboration-hub/ConnectModal.tsx(1 hunks)Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx(1 hunks)Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx(1 hunks)Frontend/src/components/collaboration-hub/CreatorSearchModal.tsx(1 hunks)Frontend/src/components/collaboration-hub/NewCollaborationModal.tsx(1 hunks)Frontend/src/components/dashboard/creator-collaborations.tsx(1 hunks)Frontend/src/components/ui/dialog.tsx(4 hunks)Frontend/src/hooks/useCollaborationState.ts(1 hunks)Frontend/src/pages/CollaborationDetails.tsx(1 hunks)Frontend/src/pages/Collaborations.tsx(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- Frontend/src/components/dashboard/creator-collaborations.tsx
- Frontend/src/components/collaboration-hub/CreatorMatchGrid.tsx
- Frontend/src/components/collaboration-hub/ActiveCollabCard.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Saahi30
PR: AOSSIE-Org/InPactAI#98
File: Frontend/src/pages/CollaborationDetails.tsx:519-521
Timestamp: 2025-07-12T20:28:04.934Z
Learning: In frontend-only commits that use mock data, hardcoded progress values and similar placeholder data are acceptable and intentional. These values serve as placeholders for UI development and will be replaced with dynamic calculations once backend integration occurs.
Frontend/src/pages/Collaborations.tsx (1)
Learnt from: Saahi30
PR: AOSSIE-Org/InPactAI#98
File: Frontend/src/pages/CollaborationDetails.tsx:519-521
Timestamp: 2025-07-12T20:28:04.934Z
Learning: In frontend-only commits that use mock data, hardcoded progress values and similar placeholder data are acceptable and intentional. These values serve as placeholders for UI development and will be replaced with dynamic calculations once backend integration occurs.
Frontend/src/pages/CollaborationDetails.tsx (1)
Learnt from: Saahi30
PR: AOSSIE-Org/InPactAI#98
File: Frontend/src/pages/CollaborationDetails.tsx:519-521
Timestamp: 2025-07-12T20:28:04.934Z
Learning: In frontend-only commits that use mock data, hardcoded progress values and similar placeholder data are acceptable and intentional. These values serve as placeholders for UI development and will be replaced with dynamic calculations once backend integration occurs.
🧬 Code Graph Analysis (12)
Frontend/src/components/collaboration-hub/CollaborationQuickActions.tsx (2)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/button.tsx (1)
Button(54-54)
Frontend/src/components/collaboration-hub/CollaborationProjectStats.tsx (2)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/separator.tsx (1)
Separator(31-31)
Frontend/src/components/collaboration-hub/CollaborationTimelineTab.tsx (4)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/calendar.tsx (1)
Calendar(73-73)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/badge.tsx (1)
Badge(36-36)
Frontend/src/components/collaboration-hub/CollaborationDeliverables.tsx (3)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/badge.tsx (1)
Badge(36-36)Frontend/src/components/ui/button.tsx (1)
Button(54-54)
Frontend/src/components/collaboration-hub/CreatorSearchModal.tsx (5)
Frontend/src/components/collaboration-hub/mockProfileData.ts (1)
mockProfileDetails(3-19)Frontend/src/components/ui/dialog.tsx (4)
Dialog(236-236)DialogContent(238-238)DialogHeader(241-241)DialogTitle(244-244)Frontend/src/components/ui/textarea.tsx (1)
Textarea(18-18)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/card.tsx (2)
Card(80-80)CardContent(85-85)
Frontend/src/components/collaboration-hub/CollaborationMessagesTab.tsx (5)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/separator.tsx (1)
Separator(31-31)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/input.tsx (1)
Input(21-21)Frontend/src/components/ui/textarea.tsx (1)
Textarea(18-18)
Frontend/src/components/collaboration-hub/CollaborationOverview.tsx (4)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/separator.tsx (1)
Separator(31-31)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/textarea.tsx (1)
Textarea(18-18)
Frontend/src/pages/Collaborations.tsx (10)
Frontend/src/hooks/useCollaborationState.ts (1)
useCollaborationState(91-159)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/mode-toggle.tsx (1)
ModeToggle(11-36)Frontend/src/components/user-nav.tsx (1)
UserNav(17-74)Frontend/src/components/ui/select.tsx (1)
Select(150-150)Frontend/src/components/ui/card.tsx (2)
CardContent(85-85)Card(80-80)Frontend/src/components/ui/tabs.tsx (4)
Tabs(52-52)TabsList(52-52)TabsTrigger(52-52)TabsContent(52-52)Frontend/src/components/dashboard/creator-collaborations.tsx (1)
mockCreatorMatches(4-56)Frontend/src/components/collaboration-hub/NewCollaborationModal.tsx (1)
NewCollaborationModal(24-292)Frontend/src/components/collaboration-hub/CreatorSearchModal.tsx (1)
CreatorSearchModal(15-115)
Frontend/src/components/collaboration-hub/CollaborationOverviewTab.tsx (4)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/separator.tsx (1)
Separator(31-31)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/textarea.tsx (1)
Textarea(18-18)
Frontend/src/components/collaboration-hub/NewCollaborationModal.tsx (1)
Frontend/src/components/collaboration-hub/mockProfileData.ts (1)
mockProfileDetails(3-19)
Frontend/src/components/ui/dialog.tsx (1)
Frontend/src/lib/utils.ts (1)
cn(4-6)
Frontend/src/components/collaboration-hub/CollaborationMessages.tsx (5)
Frontend/src/components/ui/card.tsx (4)
Card(80-80)CardHeader(81-81)CardTitle(83-83)CardContent(85-85)Frontend/src/components/ui/separator.tsx (1)
Separator(31-31)Frontend/src/components/ui/button.tsx (1)
Button(54-54)Frontend/src/components/ui/input.tsx (1)
Input(21-21)Frontend/src/components/ui/textarea.tsx (1)
Textarea(18-18)
🔇 Additional comments (13)
Frontend/src/hooks/useCollaborationState.ts (4)
4-9: Well-defined filter state interface.The
FilterStateinterface clearly defines all filter properties with consistent string typing.
22-27: Comprehensive action type definitions.The union type covers all necessary state mutations with proper type safety. The action structure is consistent and follows Redux-style patterns.
44-88: Robust reducer implementation with proper immutability.The reducer correctly handles all action types while maintaining immutability through spread operators. The default case ensures type safety.
91-159: Well-organized hook with proper memoization.The hook provides a clean API with memoized callbacks and computed values. The return object structure makes it easy to destructure only needed functionality.
Frontend/src/components/collaboration-hub/CollaboratorSidebar.tsx (1)
28-39: Hardcoded metrics are acceptable for frontend-only development.The static rating, followers, and completion rate values serve as placeholders for UI development and will be replaced with dynamic data during backend integration.
Frontend/src/components/collaboration-hub/CollaborationProjectStats.tsx (1)
14-16: Hardcoded values are acceptable for UI development.The static timeline percentage, days remaining, and files shared values serve as placeholders and will be replaced with dynamic calculations during backend integration.
Also applies to: 27-33
Frontend/src/components/collaboration-hub/CollaborationDeliverables.tsx (2)
28-30: Good status text formatting logic.The status text replacement correctly handles hyphenated statuses for better readability.
42-54: Proper conditional rendering for optional files.The component correctly handles the optional files array with proper conditional rendering and null checking.
Frontend/src/components/collaboration-hub/ConnectModal.tsx (1)
92-113: LGTM! Accessibility improvements properly implemented.The radio button group now has proper accessibility features including fieldset, legend, and proper label associations. This is a significant improvement for screen reader support.
Frontend/src/components/collaboration-hub/NewCollaborationModal.tsx (2)
24-85: Well-structured multi-step workflow with proper validation.The component effectively manages a complex multi-step process with:
- Clear step navigation with proper validation
- Good state management for form data
- Appropriate use of mock AI features for demonstration
- Proper cleanup and reset functionality
The step-by-step approach makes the complex collaboration request process manageable for users.
245-245: Good validation logic for required fields.The validation properly checks for required proposal fields before allowing progression to the review step. This ensures data completeness.
Frontend/src/components/ui/dialog.tsx (1)
7-59: Excellent accessibility improvements!The focus trap implementation and ARIA attributes significantly improve the dialog's accessibility. The focus management ensures keyboard users stay within the dialog, and the ARIA attributes provide proper semantics for screen readers.
Also applies to: 105-158
Frontend/src/components/collaboration-hub/CreatorMatchCard.tsx (1)
99-99: Great job addressing all previous review feedback!The component now has:
- Proper safety check for avatar fallback
- Comprehensive accessibility attributes (aria-label, role="progressbar", aria-live)
- Clean decomposition into subcomponents for better maintainability
Also applies to: 91-91, 48-48, 112-112
| import { Button } from "../ui/button"; | ||
| import { MessageSquare, Mail, Star, TrendingUp, Activity } from "lucide-react"; | ||
|
|
||
| export default function CollaboratorSidebar({ collaboration }: { collaboration: any }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety by defining proper interfaces.
The collaboration prop is typed as any, which reduces type safety. Consider defining proper interfaces for the collaborator data structure.
+interface Collaborator {
+ name: string;
+ avatar: string;
+ contentType: string;
+}
+
+interface Collaboration {
+ collaborator: Collaborator;
+}
+
-export default function CollaboratorSidebar({ collaboration }: { collaboration: any }) {
+export default function CollaboratorSidebar({ collaboration }: { collaboration: Collaboration }) {🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaboratorSidebar.tsx at line 8,
the collaboration prop is currently typed as any, which reduces type safety.
Define a proper TypeScript interface that describes the expected structure of
the collaboration object, including all relevant fields, and replace the any
type with this interface in the function parameter to improve type safety and
code clarity.
| import { Card, CardHeader, CardTitle, CardContent } from "../ui/card"; | ||
| import { Separator } from "../ui/separator"; | ||
|
|
||
| export default function CollaborationProjectStats({ collaboration }: { collaboration: any }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety with proper interface definitions.
The collaboration prop should have a defined interface to ensure type safety and prevent runtime errors.
+interface Deliverables {
+ completed: number;
+ total: number;
+}
+
+interface Collaboration {
+ deliverables: Deliverables;
+}
+
-export default function CollaborationProjectStats({ collaboration }: { collaboration: any }) {
+export default function CollaborationProjectStats({ collaboration }: { collaboration: Collaboration }) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export default function CollaborationProjectStats({ collaboration }: { collaboration: any }) { | |
| interface Deliverables { | |
| completed: number; | |
| total: number; | |
| } | |
| interface Collaboration { | |
| deliverables: Deliverables; | |
| } | |
| export default function CollaborationProjectStats({ collaboration }: { collaboration: Collaboration }) { |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaborationProjectStats.tsx at
line 5, replace the use of 'any' for the 'collaboration' prop with a properly
defined TypeScript interface that describes the expected shape and types of the
collaboration object. Define this interface above the component or in a separate
types file, then update the component's props to use this interface to improve
type safety and reduce runtime errors.
| const CollaborationDeliverables = ({ | ||
| mockDeliverables, | ||
| getDeliverableStatusColor, | ||
| handleViewDeliverable, | ||
| }) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Define proper TypeScript interfaces for component props.
The component props lack proper typing, which reduces type safety and IDE support. Consider defining interfaces for all props.
+interface Deliverable {
+ id: string;
+ title: string;
+ description: string;
+ status: string;
+ dueDate: string;
+ assignedTo: string;
+ files?: string[];
+}
+
+interface CollaborationDeliverablesProps {
+ mockDeliverables: Deliverable[];
+ getDeliverableStatusColor: (status: string) => string;
+ handleViewDeliverable: (deliverable: Deliverable) => void;
+}
+
-const CollaborationDeliverables = ({
- mockDeliverables,
- getDeliverableStatusColor,
- handleViewDeliverable,
-}) => (
+const CollaborationDeliverables = ({
+ mockDeliverables,
+ getDeliverableStatusColor,
+ handleViewDeliverable,
+}: CollaborationDeliverablesProps) => (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CollaborationDeliverables = ({ | |
| mockDeliverables, | |
| getDeliverableStatusColor, | |
| handleViewDeliverable, | |
| }) => ( | |
| interface Deliverable { | |
| id: string; | |
| title: string; | |
| description: string; | |
| status: string; | |
| dueDate: string; | |
| assignedTo: string; | |
| files?: string[]; | |
| } | |
| interface CollaborationDeliverablesProps { | |
| mockDeliverables: Deliverable[]; | |
| getDeliverableStatusColor: (status: string) => string; | |
| handleViewDeliverable: (deliverable: Deliverable) => void; | |
| } | |
| const CollaborationDeliverables = ({ | |
| mockDeliverables, | |
| getDeliverableStatusColor, | |
| handleViewDeliverable, | |
| }: CollaborationDeliverablesProps) => ( |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaborationDeliverables.tsx
around lines 7 to 11, the component props are currently untyped, reducing type
safety and IDE support. Define a TypeScript interface describing the shape and
types of all props (mockDeliverables, getDeliverableStatusColor,
handleViewDeliverable) and use it to type the component's props parameter. This
will improve code clarity and catch type errors early.
| <Button | ||
| size="sm" | ||
| variant="outline" | ||
| onClick={() => setShowProfile(true)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix profile viewing logic to use the correct creator.
The profile viewing always uses the first search result instead of the specific creator that was clicked. This will show incorrect profile information.
- onClick={() => setShowProfile(true)}
+ onClick={() => {
+ setSelectedCreator(creator);
+ setShowProfile(true);
+ }}And update the ViewProfileModal to use the selected creator:
<ViewProfileModal
open={showProfile}
onClose={() => setShowProfile(false)}
onConnect={() => {
setShowProfile(false);
- if (aiSearchResults.length > 0) {
- handleConnect(aiSearchResults[0]);
- }
+ if (selectedCreator) {
+ handleConnect(selectedCreator);
+ }
}}
/>You'll also need to add state for the selected creator:
+ const [selectedCreator, setSelectedCreator] = useState<any>(null);Also applies to: 108-110
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CreatorSearchModal.tsx around lines
83 and 108-110, the profile viewing logic incorrectly always uses the first
search result instead of the clicked creator. Fix this by adding state to track
the selected creator, update the onClick handler to set this selected creator
before showing the profile modal, and modify the ViewProfileModal component to
use the selected creator state for displaying profile information.
| const CollaborationMessages = ({ | ||
| mockMessages, | ||
| messageStyles, | ||
| messageStyle, | ||
| showStyleOptions, | ||
| setShowStyleOptions, | ||
| newMessage, | ||
| setNewMessage, | ||
| handleSendMessage, | ||
| handleStyleChange, | ||
| customStyle, | ||
| setCustomStyle, | ||
| handleCustomStyle, | ||
| }) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add TypeScript interface for component props
The component lacks proper TypeScript typing, reducing type safety and IntelliSense support.
Define an interface for the props:
interface CollaborationMessagesProps {
mockMessages: Array<{
id: string | number;
sender: string;
content: string;
timestamp: string;
isOwn: boolean;
}>;
messageStyles: Array<{
value: string;
label: string;
}>;
messageStyle: string;
showStyleOptions: boolean;
setShowStyleOptions: (show: boolean) => void;
newMessage: string;
setNewMessage: (message: string) => void;
handleSendMessage: () => void;
handleStyleChange: (style: string) => void;
customStyle: string;
setCustomStyle: (style: string) => void;
handleCustomStyle: () => void;
}
const CollaborationMessages: React.FC<CollaborationMessagesProps> = ({
// ... props
}) => (🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaborationMessages.tsx around
lines 9 to 22, the component props are not typed, which reduces type safety and
IntelliSense support. Define a TypeScript interface named
CollaborationMessagesProps describing all the props with their types as
specified, then update the component declaration to use React.FC with this
interface to ensure proper typing of props.
| const CollaborationOverview = ({ | ||
| collaboration, | ||
| isEditingUpdate, | ||
| editedUpdate, | ||
| handleEditUpdate, | ||
| handleSaveUpdate, | ||
| handleCancelEdit, | ||
| setEditedUpdate, | ||
| getStatusColor, | ||
| getDeliverableStatusColor, | ||
| }) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add TypeScript interface for component props
The component props lack TypeScript definitions, which reduces type safety and IDE support.
+interface CollaborationOverviewProps {
+ collaboration: {
+ startDate: string;
+ dueDate: string;
+ collaborator: {
+ contentType: string;
+ };
+ deliverables: {
+ completed: number;
+ total: number;
+ };
+ messages: number;
+ lastActivity: string;
+ latestUpdate: string;
+ };
+ isEditingUpdate: boolean;
+ editedUpdate: string;
+ handleEditUpdate: () => void;
+ handleSaveUpdate: () => void;
+ handleCancelEdit: () => void;
+ setEditedUpdate: (value: string) => void;
+ getStatusColor: (status: string) => string;
+ getDeliverableStatusColor: (status: string) => string;
+}
-const CollaborationOverview = ({
+const CollaborationOverview: React.FC<CollaborationOverviewProps> = ({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CollaborationOverview = ({ | |
| collaboration, | |
| isEditingUpdate, | |
| editedUpdate, | |
| handleEditUpdate, | |
| handleSaveUpdate, | |
| handleCancelEdit, | |
| setEditedUpdate, | |
| getStatusColor, | |
| getDeliverableStatusColor, | |
| }) => ( | |
| interface CollaborationOverviewProps { | |
| collaboration: { | |
| startDate: string; | |
| dueDate: string; | |
| collaborator: { | |
| contentType: string; | |
| }; | |
| deliverables: { | |
| completed: number; | |
| total: number; | |
| }; | |
| messages: number; | |
| lastActivity: string; | |
| latestUpdate: string; | |
| }; | |
| isEditingUpdate: boolean; | |
| editedUpdate: string; | |
| handleEditUpdate: () => void; | |
| handleSaveUpdate: () => void; | |
| handleCancelEdit: () => void; | |
| setEditedUpdate: (value: string) => void; | |
| getStatusColor: (status: string) => string; | |
| getDeliverableStatusColor: (status: string) => string; | |
| } | |
| const CollaborationOverview: React.FC<CollaborationOverviewProps> = ({ | |
| collaboration, | |
| isEditingUpdate, | |
| editedUpdate, | |
| handleEditUpdate, | |
| handleSaveUpdate, | |
| handleCancelEdit, | |
| setEditedUpdate, | |
| getStatusColor, | |
| getDeliverableStatusColor, | |
| }) => ( |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaborationOverview.tsx around
lines 8 to 18, the component props are currently untyped. Define a TypeScript
interface describing the shape and types of all props passed to
CollaborationOverview, then annotate the component's props parameter with this
interface to improve type safety and IDE support.
| <span className="font-medium">{Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100)}%</span> | ||
| </div> | ||
| <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div | ||
| className="h-3 bg-green-500 rounded-full" | ||
| style={{ width: `${(collaboration.deliverables.completed / collaboration.deliverables.total) * 100}%` }} | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add safety check for division by zero
The deliverables progress calculation could result in division by zero if collaboration.deliverables.total is 0.
- <span className="font-medium">{Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100)}%</span>
+ <span className="font-medium">{collaboration.deliverables.total > 0 ? Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100) : 0}%</span>- style={{ width: `${(collaboration.deliverables.completed / collaboration.deliverables.total) * 100}%` }}
+ style={{ width: `${collaboration.deliverables.total > 0 ? (collaboration.deliverables.completed / collaboration.deliverables.total) * 100 : 0}%` }} 📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span className="font-medium">{Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100)}%</span> | |
| </div> | |
| <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden"> | |
| <div | |
| className="h-3 bg-green-500 rounded-full" | |
| style={{ width: `${(collaboration.deliverables.completed / collaboration.deliverables.total) * 100}%` }} | |
| /> | |
| <span className="font-medium"> | |
| {collaboration.deliverables.total > 0 | |
| ? Math.round((collaboration.deliverables.completed / collaboration.deliverables.total) * 100) | |
| : 0}% | |
| </span> | |
| </div> | |
| <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden"> | |
| <div | |
| className="h-3 bg-green-500 rounded-full" | |
| style={{ | |
| width: `${ | |
| collaboration.deliverables.total > 0 | |
| ? (collaboration.deliverables.completed / collaboration.deliverables.total) * 100 | |
| : 0 | |
| }%`, | |
| }} | |
| /> |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/CollaborationOverview.tsx around
lines 136 to 142, the calculation for deliverables progress divides by
collaboration.deliverables.total without checking if it is zero, which can cause
a division by zero error. Add a safety check to ensure
collaboration.deliverables.total is greater than zero before performing the
division; if it is zero, default the progress percentage to 0 to avoid runtime
errors.
| {showDeliverableModal && selectedDeliverable && ( | ||
| <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"> | ||
| <div className="bg-white rounded-lg w-full max-w-4xl h-[90vh] flex flex-col"> | ||
| <div className="flex items-center justify-between p-4 border-b border-gray-200"> | ||
| <h3 className="text-2xl font-bold text-gray-900">Deliverable Details</h3> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={handleCloseDeliverableModal} | ||
| className="h-8 w-8 p-0" | ||
| > | ||
| <X className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| <div className="flex-1 overflow-y-auto p-4"> | ||
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | ||
| {/* Main Content */} | ||
| <div className="lg:col-span-2 space-y-6"> | ||
| {/* Deliverable Details */} | ||
| <Card> | ||
| <CardHeader> | ||
| <div className="mb-1"> | ||
| <span className="text-xs font-semibold uppercase tracking-wider text-gray-500">Deliverable Details</span> | ||
| </div> | ||
| <CardTitle className="text-2xl font-bold text-gray-900 mb-1"> | ||
| {selectedDeliverable.title} | ||
| </CardTitle> | ||
| <p className="text-sm text-gray-600 mt-1">{selectedDeliverable.description}</p> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Status & Progress</h4> | ||
| <div className="space-y-2"> | ||
| <div className="flex items-center gap-2"> | ||
| <Badge className={getDeliverableStatusColor(selectedDeliverable.status)}> | ||
| {selectedDeliverable.status.replace('-', ' ')} | ||
| </Badge> | ||
| <span className="text-sm text-gray-600"> | ||
| {selectedDeliverable.status === 'completed' ? '100%' : | ||
| selectedDeliverable.status === 'in-progress' ? '65%' : '0%'} complete | ||
| </span> | ||
| </div> | ||
| {selectedDeliverable.status === 'in-progress' && ( | ||
| <div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden"> | ||
| <div className="h-2 bg-blue-500 rounded-full" style={{ width: "65%" }} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <h4 className="font-semibold text-gray-900 mb-2">Timeline</h4> | ||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Due Date:</span> | ||
| <span className="font-medium">{selectedDeliverable.dueDate}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Assigned To:</span> | ||
| <span className="font-medium">{selectedDeliverable.assignedTo}</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Created:</span> | ||
| <span className="font-medium">{collaboration.startDate}</span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Files & Attachments */} | ||
| {selectedDeliverable.files && selectedDeliverable.files.length > 0 && ( | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <Download className="h-5 w-5" /> | ||
| Files & Attachments | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-3"> | ||
| {selectedDeliverable.files.map((file, index) => ( | ||
| <div key={index} className="flex items-center justify-between p-3 border border-gray-200 rounded-lg"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center"> | ||
| <FileText className="h-5 w-5 text-gray-600" /> | ||
| </div> | ||
| <div> | ||
| <p className="font-medium text-gray-900">{file}</p> | ||
| <p className="text-sm text-gray-600">Uploaded 2 days ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Button variant="outline" size="sm"> | ||
| <Eye className="h-4 w-4 mr-1" /> | ||
| Preview | ||
| </Button> | ||
| <Button variant="outline" size="sm"> | ||
| <Download className="h-4 w-4 mr-1" /> | ||
| Download | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| )} | ||
|
|
||
| {/* Comments & Feedback */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="flex items-center gap-2"> | ||
| <MessageSquare className="h-5 w-5" /> | ||
| Comments & Feedback | ||
| </CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-4"> | ||
| <div className="bg-gray-50 p-3 rounded-lg"> | ||
| <div className="flex items-center gap-2 mb-2"> | ||
| <Avatar className="h-6 w-6"> | ||
| <AvatarImage src={collaboration.collaborator.avatar} alt={collaboration.collaborator.name} /> | ||
| <AvatarFallback className="text-xs">{collaboration.collaborator.name.slice(0, 2).toUpperCase()}</AvatarFallback> | ||
| </Avatar> | ||
| <span className="text-sm font-medium">{collaboration.collaborator.name}</span> | ||
| <span className="text-xs text-gray-500">2 days ago</span> | ||
| </div> | ||
| <p className="text-sm text-gray-700"> | ||
| "The first draft is ready for review. I've included the main product features | ||
| and added some B-roll footage. Let me know if you'd like any adjustments to the pacing." | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="bg-blue-50 p-3 rounded-lg"> | ||
| <div className="flex items-center gap-2 mb-2"> | ||
| <Avatar className="h-6 w-6"> | ||
| <AvatarFallback className="text-xs bg-blue-100">You</AvatarFallback> | ||
| </Avatar> | ||
| <span className="text-sm font-medium">You</span> | ||
| <span className="text-xs text-gray-500">1 day ago</span> | ||
| </div> | ||
| <p className="text-sm text-gray-700"> | ||
| "Great work! The pacing looks good. Could you add a few more close-up shots | ||
| of the product features around the 1:30 mark?" | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="flex gap-2 mt-4"> | ||
| <Textarea | ||
| placeholder="Add a comment or feedback..." | ||
| className="flex-1" | ||
| rows={2} | ||
| /> | ||
| <Button size="sm"> | ||
| <Send className="h-4 w-4" /> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
|
|
||
| {/* Sidebar */} | ||
| <div className="space-y-6"> | ||
| {/* Quick Actions */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Quick Actions</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-2"> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Edit className="h-4 w-4 mr-2" /> | ||
| Edit Deliverable | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <Download className="h-4 w-4 mr-2" /> | ||
| Download All Files | ||
| </Button> | ||
| <Button variant="outline" className="w-full justify-start" size="sm"> | ||
| <MessageSquare className="h-4 w-4 mr-2" /> | ||
| Send Message | ||
| </Button> | ||
| {selectedDeliverable.status !== 'completed' && ( | ||
| <Button className="w-full justify-start" size="sm"> | ||
| <CheckCircle className="h-4 w-4 mr-2" /> | ||
| Mark Complete | ||
| </Button> | ||
| )} | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Deliverable Stats */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Deliverable Stats</CardTitle> | ||
| </CardHeader> | ||
| <CardContent className="space-y-4"> | ||
| <div className="grid grid-cols-2 gap-4 text-center"> | ||
| <div> | ||
| <div className="text-2xl font-bold text-blue-600"> | ||
| {selectedDeliverable.status === 'completed' ? '100%' : | ||
| selectedDeliverable.status === 'in-progress' ? '65%' : '0%'} | ||
| </div> | ||
| <div className="text-xs text-gray-600">Progress</div> | ||
| </div> | ||
| <div> | ||
| <div className="text-2xl font-bold text-green-600"> | ||
| {selectedDeliverable.files?.length || 0} | ||
| </div> | ||
| <div className="text-xs text-gray-600">Files</div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <Separator /> | ||
|
|
||
| <div className="space-y-2 text-sm"> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Days Remaining:</span> | ||
| <span className="font-medium">3 days</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Comments:</span> | ||
| <span className="font-medium">2</span> | ||
| </div> | ||
| <div className="flex justify-between"> | ||
| <span className="text-gray-600">Last Updated:</span> | ||
| <span className="font-medium">1 day ago</span> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Version History */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>Version History</CardTitle> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <div className="space-y-3"> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-green-500 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.2 - Final Draft</p> | ||
| <p className="text-xs text-gray-600">Updated 1 day ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-blue-500 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.1 - First Review</p> | ||
| <p className="text-xs text-gray-600">Updated 2 days ago</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center gap-3"> | ||
| <div className="w-2 h-2 bg-gray-300 rounded-full"></div> | ||
| <div className="flex-1"> | ||
| <p className="text-sm font-medium">v1.0 - Initial Draft</p> | ||
| <p className="text-xs text-gray-600">Created 3 days ago</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Extract deliverable modal into a separate component
This modal component is quite large (270+ lines) and handles multiple responsibilities. Consider extracting it into a dedicated component for better maintainability.
Create Frontend/src/components/collaboration-hub/DeliverableModal.tsx:
interface DeliverableModalProps {
isOpen: boolean;
deliverable: Deliverable | null;
collaboration: any;
onClose: () => void;
getDeliverableStatusColor: (status: string) => string;
}
export default function DeliverableModal({ isOpen, deliverable, collaboration, onClose, getDeliverableStatusColor }: DeliverableModalProps) {
if (!isOpen || !deliverable) return null;
// Modal content here
}🤖 Prompt for AI Agents
In Frontend/src/pages/CollaborationDetails.tsx around lines 583 to 854, the
deliverable modal JSX is very large and mixes multiple concerns, making the file
hard to maintain. Extract this modal code into a new component file
Frontend/src/components/collaboration-hub/DeliverableModal.tsx. Create a
DeliverableModal component that accepts props for isOpen, deliverable,
collaboration, onClose, and getDeliverableStatusColor. Move all modal JSX and
related logic into this component, returning null if not open or deliverable is
null. Then replace the modal JSX in CollaborationDetails.tsx with a call to this
new component, passing the required props.
| <div className="flex-1 p-4"> | ||
| <iframe | ||
| src={contractUrl} | ||
| className="w-full h-full border border-gray-200 rounded" | ||
| title="Collaboration Contract" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for iframe loading
The PDF iframe lacks error handling if the contract URL fails to load or is invalid.
+ <div className="relative w-full h-full">
<iframe
src={contractUrl}
className="w-full h-full border border-gray-200 rounded"
title="Collaboration Contract"
+ onError={() => console.error('Failed to load contract')}
+ onLoad={() => console.log('Contract loaded successfully')}
/>
+ <div className="absolute inset-0 bg-gray-100 flex items-center justify-center pointer-events-none opacity-0 transition-opacity duration-300" id="iframe-loader">
+ <div className="text-gray-600">Loading contract...</div>
+ </div>
+ </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="flex-1 p-4"> | |
| <iframe | |
| src={contractUrl} | |
| className="w-full h-full border border-gray-200 rounded" | |
| title="Collaboration Contract" | |
| /> | |
| </div> | |
| <div className="flex-1 p-4"> | |
| <div className="relative w-full h-full"> | |
| <iframe | |
| src={contractUrl} | |
| className="w-full h-full border border-gray-200 rounded" | |
| title="Collaboration Contract" | |
| onError={() => console.error('Failed to load contract')} | |
| onLoad={() => console.log('Contract loaded successfully')} | |
| /> | |
| <div | |
| id="iframe-loader" | |
| className="absolute inset-0 bg-gray-100 flex items-center justify-center | |
| pointer-events-none opacity-0 transition-opacity duration-300" | |
| > | |
| <div className="text-gray-600">Loading contract...</div> | |
| </div> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In Frontend/src/pages/CollaborationDetails.tsx around lines 873 to 879, the
iframe displaying the contract PDF does not handle errors if the contractUrl
fails to load or is invalid. Add error handling by implementing an onError event
handler on the iframe that can display a fallback UI or error message to inform
the user that the contract could not be loaded. This will improve user
experience by gracefully handling load failures.
chandansgowda
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check and resolve relevant coderabbit reviews.
was planning to do that in the backend push....but ur right will do it here....and now will open a separate branch for the backend |
…ub grid and card components - Added <label> and aria-label to status and sort selects in ActiveCollabsGrid - Added aria-labels and focus styles to action buttons in ActiveCollabCard - Confirmed ConnectModal and CreatorMatchCard have required ARIA attributes and subcomponents for accessibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (1)
Frontend/src/components/collaboration-hub/ConnectModal.tsx (1)
92-94: Remove redundant empty state check.The empty state check inside the map is redundant since you already handle the
totalIdeas === 0case with an early return.-{ideasToShow.length === 0 ? ( - <li className="text-gray-500 text-sm">No collaboration ideas available.</li> -) : ( - ideasToShow.map((idea, idx) => ( +{ideasToShow.map((idea, idx) => ( <li key={idx} className="bg-gray-50 rounded-lg p-3 border border-gray-100"> <div className="font-semibold text-gray-800">{idea.title}</div> <div className="text-xs text-gray-600">{idea.description}</div> </li> - )) -)} + ))}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Frontend/src/components/collaboration-hub/ConnectModal.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Saahi30
PR: AOSSIE-Org/InPactAI#98
File: Frontend/src/pages/CollaborationDetails.tsx:519-521
Timestamp: 2025-07-12T20:28:05.017Z
Learning: In frontend-only commits that use mock data, hardcoded progress values and similar placeholder data are acceptable and intentional. These values serve as placeholders for UI development and will be replaced with dynamic calculations once backend integration occurs.
Learnt from: Saahi30
PR: AOSSIE-Org/InPactAI#98
File: Frontend/src/pages/CollaborationDetails.tsx:234-234
Timestamp: 2025-07-14T11:45:01.846Z
Learning: In frontend-only PRs, hardcoded URLs (like dummy PDF URLs for contracts) and mock data are acceptable and intentional placeholders for UI development, to be replaced during backend integration phase.
🔇 Additional comments (1)
Frontend/src/components/collaboration-hub/ConnectModal.tsx (1)
27-27: Add error handling for mock profile data.The component assumes
mockProfileDetailsexists but doesn't handle potential undefined cases.-const profile = mockProfileDetails; +const profile = mockProfileDetails || { name: "Unknown", avatar: "", contentType: "Creator" };⛔ Skipped due to learnings
Learnt from: Saahi30 PR: AOSSIE-Org/InPactAI#98 File: Frontend/src/pages/CollaborationDetails.tsx:519-521 Timestamp: 2025-07-12T20:28:05.017Z Learning: In frontend-only commits that use mock data, hardcoded progress values and similar placeholder data are acceptable and intentional. These values serve as placeholders for UI development and will be replaced with dynamic calculations once backend integration occurs.
|
|
||
| const ConnectModal: React.FC<ConnectModalProps> = ({ open, onClose, onSend, matchPercentage = defaultMatch, whyMatch = mockWhyMatch }) => { | ||
| const [ideasPage, setIdeasPage] = useState(0); | ||
| const [selectedText, setSelectedText] = useState(mockRequestTexts[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add defensive programming for mock data access.
The component assumes mockRequestTexts[0] exists, which could cause a runtime error if the array is empty.
-const [selectedText, setSelectedText] = useState(mockRequestTexts[0]);
+const [selectedText, setSelectedText] = useState(mockRequestTexts[0] || "");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [selectedText, setSelectedText] = useState(mockRequestTexts[0]); | |
| const [selectedText, setSelectedText] = useState(mockRequestTexts[0] || ""); |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/ConnectModal.tsx at line 25, the
code assumes mockRequestTexts[0] exists, which can cause a runtime error if the
array is empty. Add a defensive check to ensure mockRequestTexts has at least
one element before accessing index 0; if empty, initialize selectedText with a
safe default value such as an empty string or null to prevent errors.
| </div> | ||
| <div className="flex gap-2 w-full mt-auto justify-center pt-2 pb-2"> | ||
| <Button className="bg-gray-100 text-gray-900 hover:bg-gray-200 flex-1 font-semibold rounded-full py-2" variant="secondary" onClick={onClose}>Cancel</Button> | ||
| <Button className="bg-yellow-400 text-white hover:bg-yellow-500 flex-1 font-semibold rounded-full py-2" onClick={() => onSend(selectedText)}>Send Request</Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add safety check before sending request.
The send button should be disabled when no message is selected to prevent sending empty requests.
-<Button className="bg-yellow-400 text-white hover:bg-yellow-500 flex-1 font-semibold rounded-full py-2" onClick={() => onSend(selectedText)}>Send Request</Button>
+<Button
+ className="bg-yellow-400 text-white hover:bg-yellow-500 flex-1 font-semibold rounded-full py-2 disabled:opacity-50 disabled:cursor-not-allowed"
+ onClick={() => onSend(selectedText)}
+ disabled={!selectedText.trim()}
+>
+ Send Request
+</Button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button className="bg-yellow-400 text-white hover:bg-yellow-500 flex-1 font-semibold rounded-full py-2" onClick={() => onSend(selectedText)}>Send Request</Button> | |
| <Button | |
| className="bg-yellow-400 text-white hover:bg-yellow-500 flex-1 font-semibold rounded-full py-2 disabled:opacity-50 disabled:cursor-not-allowed" | |
| onClick={() => onSend(selectedText)} | |
| disabled={!selectedText.trim()} | |
| > | |
| Send Request | |
| </Button> |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/ConnectModal.tsx at line 138, the
Send Request button currently allows sending requests even when no message is
selected. Add a condition to disable the button when selectedText is empty or
undefined to prevent sending empty requests. Update the Button component's
disabled prop accordingly to reflect this safety check.
| {mockRequestTexts.map((text, idx) => { | ||
| const radioId = `requestText-${idx}`; | ||
| return ( | ||
| <div key={radioId} className="flex items-start gap-2 cursor-pointer"> | ||
| <input | ||
| type="radio" | ||
| id={radioId} | ||
| name="requestText" | ||
| value={text} | ||
| checked={selectedText === text} | ||
| onChange={() => setSelectedText(text)} | ||
| className="mt-1 accent-yellow-400" | ||
| /> | ||
| <label htmlFor={radioId} className={`text-sm px-4 py-2 rounded-2xl inline-block ${selectedText === text ? 'bg-yellow-100 text-yellow-900 border border-yellow-300' : 'bg-gray-100 text-gray-700'}`}> | ||
| {text} | ||
| </label> | ||
| </div> | ||
| ); | ||
| })} | ||
| </fieldset> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add safety check for empty message texts.
The component should handle the case where mockRequestTexts might be empty to prevent rendering issues.
<fieldset className="space-y-2 border-0 p-0 m-0">
<legend className="sr-only">Select a message to send</legend>
+ {mockRequestTexts.length === 0 ? (
+ <div className="text-gray-500 text-sm">No message templates available.</div>
+ ) : (
{mockRequestTexts.map((text, idx) => {
const radioId = `requestText-${idx}`;
return (
<div key={radioId} className="flex items-start gap-2 cursor-pointer">
<input
type="radio"
id={radioId}
name="requestText"
value={text}
checked={selectedText === text}
onChange={() => setSelectedText(text)}
className="mt-1 accent-yellow-400"
/>
<label htmlFor={radioId} className={`text-sm px-4 py-2 rounded-2xl inline-block ${selectedText === text ? 'bg-yellow-100 text-yellow-900 border border-yellow-300' : 'bg-gray-100 text-gray-700'}`}>
{text}
</label>
</div>
);
})}
+ )}
</fieldset>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/ConnectModal.tsx around lines 115
to 134, add a safety check before mapping over mockRequestTexts to ensure it is
not empty or undefined. If mockRequestTexts is empty, render a fallback UI or
return null to prevent rendering issues. This can be done by conditionally
rendering the map block only when mockRequestTexts has elements.
| import React, { useState } from "react"; | ||
| import { mockProfileDetails, mockCollabIdeas, mockRequestTexts, mockWhyMatch } from "./mockProfileData"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add missing useEffect import for keyboard accessibility.
The component needs useEffect import to implement escape key handling for better accessibility, which was suggested in previous reviews but appears to be missing from the current implementation.
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";Then add escape key handling after line 25:
const [selectedText, setSelectedText] = useState(mockRequestTexts[0]);
+
+ useEffect(() => {
+ const handleEscape = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ onClose();
+ }
+ };
+ if (open) {
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }
+ }, [open, onClose]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import React, { useState } from "react"; | |
| import { mockProfileDetails, mockCollabIdeas, mockRequestTexts, mockWhyMatch } from "./mockProfileData"; | |
| import React, { useState, useEffect } from "react"; | |
| import { mockProfileDetails, mockCollabIdeas, mockRequestTexts, mockWhyMatch } from "./mockProfileData"; | |
| function ConnectModal({ open, onClose, /* …other props */ }) { | |
| const [selectedText, setSelectedText] = useState(mockRequestTexts[0]); | |
| useEffect(() => { | |
| const handleEscape = (e: KeyboardEvent) => { | |
| if (e.key === 'Escape') { | |
| onClose(); | |
| } | |
| }; | |
| if (open) { | |
| document.addEventListener('keydown', handleEscape); | |
| return () => document.removeEventListener('keydown', handleEscape); | |
| } | |
| }, [open, onClose]); | |
| // …rest of component | |
| } |
🤖 Prompt for AI Agents
In Frontend/src/components/collaboration-hub/ConnectModal.tsx at lines 1-2, the
React import statement is missing the useEffect hook, which is necessary for
implementing escape key handling for accessibility. Add useEffect to the import
from React. Then, after line 25, implement an effect that adds a keydown event
listener to detect the Escape key and triggers the appropriate handler to close
the modal, ensuring to clean up the event listener on component unmount.
|
I have merged it for now. As discussed on call, we will have to improve the code quality and have a check for issues once the MVP is built. |
|
Okay... |
Closes #95
📝 Description
This pull request implements the entire frontend for the AI Creator Collaboration Hub.
It provides a modern, interactive UI for creator matching, active collaborations, and collaboration requests, enabling users to discover, manage, and track creator partnerships efficiently.
🔧 Changes Made(more to follow)
📷 Screenshots or Visual Changes
✅ Checklist
Summary by CodeRabbit
New Features
Enhancements