Components
Reusable React components organized by feature and functionality.
Component Organization
components/
├── chat/ # Chat feature components
├── layout/ # Layout structure
├── analytics/ # Analytics dashboard
├── marketing/ # Public marketing components (PricingSection)
├── sources/ # Data source management (file, url, confluence, MCP)
├── providers/ # Context providers
├── common/ # Shared/utility components
├── theme/ # Theme switching
└── ui/ # Radix UI primitives
Chat Components
ChatInterface
Main chat container managing conversation flow.
// components/chat/chat-interface.tsx
interface ChatInterfaceProps {
conversationId?: string;
}
export function ChatInterface({ conversationId }: ChatInterfaceProps) {
// Manages conversation state, streaming, and messages
}
MessageList
Renders conversation messages with virtualization.
interface MessageListProps {
messages: Message[];
isLoading?: boolean;
}
MessageBubble
Individual message display with formatting.
interface MessageBubbleProps {
message: Message;
isStreaming?: boolean;
}
Features:
- Markdown rendering
- Code syntax highlighting
- Copy to clipboard
- Feedback buttons
StreamingMessage
Handles progressive content display during streaming.
interface StreamingMessageProps {
content: string;
status: 'idle' | 'connecting' | 'streaming' | 'complete' | 'error';
sources: Source[];
}
SourceCitations
Displays document sources for AI responses.
interface SourceCitationsProps {
sources: Source[];
expandable?: boolean;
}
ChatInput
Message input with submit handling.
interface ChatInputProps {
onSubmit: (message: string) => void;
disabled?: boolean;
placeholder?: string;
}
Layout Components
Sidebar
Main navigation sidebar with collapsible sections.
// components/layout/sidebar.tsx
export function Sidebar() {
const { sidebarCollapsed } = useUIStore();
const { role } = useWorkspaceRole();
return (
<nav className={cn(
"flex flex-col h-full",
sidebarCollapsed && "w-16"
)}>
<WorkspaceSwitcher />
<NavLinks role={role} />
<UserNav />
</nav>
);
}
MobileSidebar
Sheet-based sidebar for mobile devices.
export function MobileSidebar() {
const { sidebarOpen, setSidebarOpen } = useUIStore();
return (
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
<SheetContent side="left">
<Sidebar />
</SheetContent>
</Sheet>
);
}
Header
Top navigation with search and user menu.
export function Header() {
return (
<header className="h-16 border-b flex items-center justify-between px-4">
<Breadcrumbs />
<div className="flex items-center gap-4">
<SearchCommand />
<NotificationBell />
<UserNav />
</div>
</header>
);
}
WorkspaceSwitcher
Dropdown for switching between workspaces.
export function WorkspaceSwitcher() {
const { workspaces, activeWorkspaceId, setActiveWorkspace } = useWorkspaceStore();
const activeWorkspace = useActiveWorkspace();
return (
<DropdownMenu>
<DropdownMenuTrigger>
{activeWorkspace?.name}
</DropdownMenuTrigger>
<DropdownMenuContent>
{workspaces.map(workspace => (
<DropdownMenuItem
key={workspace._id}
onClick={() => setActiveWorkspace(workspace._id)}
>
{workspace.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
Analytics Components
StatsCards
Key metrics display.
interface StatsCardsProps {
stats: {
totalQueries: number;
avgResponseTime: number;
userSatisfaction: number;
documentsIndexed: number;
};
}
UsageChart
Query volume over time (Recharts).
interface UsageChartProps {
data: {
date: string;
queries: number;
}[];
period: 'day' | 'week' | 'month';
}
FeedbackChart
User feedback distribution.
interface FeedbackChartProps {
positive: number;
negative: number;
neutral: number;
}
PopularQuestions
Most frequently asked questions.
interface PopularQuestionsProps {
questions: {
question: string;
count: number;
avgConfidence: number;
}[];
}
Sources Components
Components in components/sources/ drive the Sources page (/sources) where users connect and manage all data sources.
DataSourceCard
Card for file, URL, and Confluence sources (native ingestion).
interface DataSourceCardProps {
source: DataSource; // status, stats, lastSyncedAt
workspaceId: string;
}
Shows source type icon, status badge (pending / syncing / active / error), document count, last sync date, and sync/delete actions gated by RequirePermission.
FileUploadDialog
Dialog for uploading a PDF, DOCX, or XLSX file (max 25 MB). Submits as multipart/form-data via sourcesApi.create().
UrlAddDialog
Dialog for indexing a public web URL. Submits as JSON { sourceType: 'url', config: { url } }.
ConfluenceConnectDialog
Dialog for connecting Confluence Cloud. Fields: base URL, space key, email, API token (password). Submits as JSON with encrypted API token stored server-side.
MCPServerCard
Card for MCP-connected external servers.
interface MCPServerCardProps {
source: MCPSource;
workspaceId: string;
}
- Source type icons:
Layers(Confluence),HardDrive(Google Drive),GitBranch(GitHub),TicketCheck(Jira),MessageSquare(Slack),Plug(custom) - Status badges: pending · syncing (animated) · active · paused · error
- Shows documents indexed, last sync date, auto-sync interval, and last error string
- Sync and delete buttons gated by
RequirePermission permission="canTriggerSync"
MCPConnectDialog
Dialog for registering a new MCP server. Fields: name, source type (Select), server URL + inline "Test" button, auth token (optional, password), auto-sync toggle (Switch), sync interval hours.
The Test button calls mcpApi.testConnection() directly (not via React Query mutation) and displays green/red inline feedback below the URL field before the user submits.
interface MCPConnectDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
workspaceId: string;
}
Marketing Components
Components in components/marketing/ are used exclusively on public (unauthenticated) pages.
They have no auth dependency and can be rendered as client or server components as needed.
PricingSection
components/marketing/pricing-section.tsx — shared pricing grid used by both the landing page
(/) and the standalone /pricing route.
// No props required — all data is static
export function PricingSection() { ... }
Features:
- Billing toggle — monthly / annual pill buttons; annual shows a green
−20%badge - 4-plan grid — Starter, Professional, Business, Enterprise in a
sm:grid-cols-2 lg:grid-cols-4layout - Highlighted plan — Professional has
ring-2 ring-primaryborder + absolute "Most Popular" badge - Feature rows — 10 rows per card (vendors, members, DORA assessments, questionnaires, AI Copilot queries, data sources, EBA export, cert/contract alerts, support, free trial)
- Animations —
framer-motionwhileInViewfade-up with 0.1 s stagger per card
Plan data:
| Plan | Monthly | Annual | Highlight |
|---|---|---|---|
| Starter | €199/mo | €159/mo | — |
| Professional | €499/mo | €399/mo | Most Popular |
| Business | €999/mo | €799/mo | — |
| Enterprise | Custom | Custom | — |
CTA routing: Enterprise → /contact; all others → /register.
Common Components
RequirePermission
Permission guard component.
interface RequirePermissionProps {
permission: 'canQuery' | 'canViewSources' | 'canInvite' | 'canManageSync';
fallback?: React.ReactNode;
children: React.ReactNode;
}
export function RequirePermission({
permission,
fallback = null,
children,
}: RequirePermissionProps) {
const permissions = usePermissions();
if (!permissions[permission]) {
return fallback;
}
return children;
}
RequireRole
Role-based access guard.
interface RequireRoleProps {
role: 'admin' | 'owner';
fallback?: React.ReactNode;
children: React.ReactNode;
}
RequireWorkspaceRole
Workspace-specific role guard.
interface RequireWorkspaceRoleProps {
roles: ('owner' | 'admin' | 'member' | 'viewer')[];
fallback?: React.ReactNode;
children: React.ReactNode;
}
ViewerBanner
Read-only mode indicator.
export function ViewerBanner() {
const { role } = useWorkspaceRole();
if (role !== 'viewer') return null;
return (
<Alert variant="info">
You have view-only access to this workspace.
</Alert>
);
}
UI Components (Radix)
Radix UI primitives styled with Tailwind:
| Component | Description |
|---|---|
Button | Action buttons with variants |
Dialog | Modal dialogs |
DropdownMenu | Context menus |
Input | Text inputs |
Select | Dropdown selects |
Table | Data tables |
Tabs | Tab navigation |
Toast | Notifications (Sonner) |
Sheet | Side panels |
Card | Content containers |
Badge | Status indicators |
Avatar | User avatars |
Skeleton | Loading placeholders |
Progress | Progress bars |
Tooltip | Hover tooltips |
Component Patterns
Loading States
function DataComponent() {
const { data, isLoading } = useQuery(...);
if (isLoading) {
return <Skeleton className="h-20 w-full" />;
}
return <DataDisplay data={data} />;
}
Error Boundaries
// components/error-boundary.tsx
export function ErrorBoundary({ children }) {
return (
<ErrorBoundaryPrimitive fallback={<ErrorFallback />}>
{children}
</ErrorBoundaryPrimitive>
);
}
function ErrorFallback({ error, reset }) {
return (
<div className="p-4 text-center">
<h2>Something went wrong</h2>
<Button onClick={reset}>Try again</Button>
</div>
);
}
Form Handling
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
function SettingsForm() {
const form = useForm({
resolver: zodResolver(settingsSchema),
defaultValues: { ... },
});
return (
<Form {...form}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form>
);
}
Responsive Design
function ResponsiveComponent() {
const { isMobile } = useUIStore();
return (
<div className={cn(
"grid gap-4",
isMobile ? "grid-cols-1" : "grid-cols-3"
)}>
{/* Content */}
</div>
);
}