Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
DialogHeader,
DialogTitle,
} from '@comp/ui/dialog';
import { Input } from '@comp/ui/input';
import { ArrowRight, Search, Sparkles, X } from 'lucide-react';
import { ArrowRight, Sparkles } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import { useParams } from 'next/navigation';
Expand All @@ -23,6 +22,7 @@ import {
type Integration,
type IntegrationCategory,
} from '../data/integrations';
import { SearchInput } from './SearchInput';

const LOGO_TOKEN = 'pk_AZatYxV5QDSfWpRDaBxzRQ';

Expand Down Expand Up @@ -63,27 +63,16 @@ export function IntegrationsGrid() {
};

return (
<div className="space-y-8">
<div className="space-y-4">
{/* Search and Filters */}
<div className="space-y-4">
<div className="flex items-center gap-2">
{/* Search Bar */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search integrations..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2"
>
<X className="w-4 h-4 text-muted-foreground hover:text-foreground" />
</button>
)}
</div>
<SearchInput
value={searchQuery}
onChange={setSearchQuery}
placeholder="Search integrations..."
className="w-80 flex-shrink-0"
/>

{/* Category Filters */}
<div className="flex gap-2 flex-wrap">
Expand All @@ -107,59 +96,60 @@ export function IntegrationsGrid() {
</div>
</div>

{/* Results info - only show when filtering */}
{(searchQuery || selectedCategory !== 'All') && filteredIntegrations.length > 0 && (
<div className="text-sm text-muted-foreground">
Showing {filteredIntegrations.length}{' '}
{filteredIntegrations.length === 1 ? 'match' : 'matches'}
</div>
)}

{/* Integration Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredIntegrations.map((integration) => (
<Card
key={integration.id}
className="group relative overflow-hidden hover:shadow-md transition-all hover:border-primary/30 cursor-pointer"
onClick={() => setSelectedIntegration(integration)}
>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-background border border-border flex items-center justify-center overflow-hidden">
<Image
src={`https://img.logo.dev/${integration.domain}?token=${LOGO_TOKEN}`}
alt={`${integration.name} logo`}
width={32}
height={32}
unoptimized
className="object-contain rounded-md"
/>
</div>
<div>
<CardTitle className="text-base font-semibold flex items-center gap-2">
{integration.name}
{integration.popular && (
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
Popular
</Badge>
)}
</CardTitle>
<p className="text-xs text-muted-foreground mt-0.5">{integration.category}</p>
<div className="space-y-2">
{/* Integration Cards */}
{(searchQuery || selectedCategory !== 'All') && filteredIntegrations.length > 0 && (
<div className="text-sm text-muted-foreground">
Showing {filteredIntegrations.length}{' '}
{filteredIntegrations.length === 1 ? 'match' : 'matches'}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Results info - only show when filtering */}
{filteredIntegrations.map((integration) => (
<Card
key={integration.id}
className="group relative overflow-hidden hover:shadow-md transition-all hover:border-primary/30 cursor-pointer"
onClick={() => setSelectedIntegration(integration)}
>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-background border border-border flex items-center justify-center overflow-hidden">
<Image
src={`https://img.logo.dev/${integration.domain}?token=${LOGO_TOKEN}`}
alt={`${integration.name} logo`}
width={32}
height={32}
unoptimized
className="object-contain rounded-md"
/>
</div>
<div>
<CardTitle className="text-base font-semibold flex items-center gap-2">
{integration.name}
{integration.popular && (
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
Popular
</Badge>
)}
</CardTitle>
<p className="text-xs text-muted-foreground mt-0.5">{integration.category}</p>
</div>
</div>
</div>
</div>
</CardHeader>
<CardContent>
<CardDescription className="text-sm leading-relaxed line-clamp-2">
{integration.description}
</CardDescription>
</CardContent>
</CardHeader>
<CardContent>
<CardDescription className="text-sm leading-relaxed line-clamp-2">
{integration.description}
</CardDescription>
</CardContent>

{/* Hover overlay */}
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
</Card>
))}
{/* Hover overlay */}
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
</Card>
))}
</div>
</div>

{/* Empty state - opportunity, not limitation */}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { cn } from '@comp/ui/utils/cn';
import { Search, X } from 'lucide-react';

interface SearchInputProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
className?: string;
}

export function SearchInput({ value, onChange, placeholder, className }: SearchInputProps) {
return (
<div className={cn('relative w-full py-1 rounded-md border border-input', className)}>
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Search className="w-4 h-4 text-muted-foreground" />
</div>
<input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="pl-10 pr-10"
/>
{value && (
<button
onClick={() => onChange('')}
className="absolute inset-y-0 right-0 flex items-center pr-3"
type="button"
>
<X className="w-4 h-4 text-muted-foreground hover:text-foreground transition-colors" />
</button>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion apps/app/src/app/(app)/[orgId]/integrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function IntegrationsPage() {
<div className="container mx-auto p-8">
<div className="max-w-7xl mx-auto space-y-10">
{/* Header */}
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-baseline gap-3">
<h1 className="text-3xl font-bold tracking-tight">Integrations</h1>
<span className="text-2xl text-muted-foreground/40 font-light">∞</span>
Expand Down
24 changes: 20 additions & 4 deletions apps/app/src/app/(app)/onboarding/[orgId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog';
import { OnboardingLayout } from '@/components/onboarding/OnboardingLayout';
import { MinimalHeader } from '@/components/layout/MinimalHeader';
import { auth } from '@/utils/auth';
import { db } from '@db';
import { headers } from 'next/headers';
import { notFound } from 'next/navigation';
import { OnboardingSidebar } from '../../setup/components/OnboardingSidebar';

interface OnboardingRouteLayoutProps {
children: React.ReactNode;
Expand Down Expand Up @@ -42,9 +43,24 @@ export default async function OnboardingRouteLayout({
}

return (
<OnboardingLayout variant="onboarding" currentOrganization={organization}>
{children}
<main className="flex min-h-dvh flex-col">
<div className="flex flex-1 min-h-0">
{/* Form Section - Left Side */}
<div className="flex-1 flex flex-col">
<MinimalHeader
user={session.user}
organizations={[]}
currentOrganization={organization}
/>
{children}
</div>

{/* Sidebar Section - Right Side, Hidden on Mobile */}
<div className="hidden md:flex md:w-1/2 min-h-screen bg-[#FAFAFA] items-end justify-center py-16 px-8">
<OnboardingSidebar className="w-full max-w-xl mx-auto h-1/2 mt-auto" />
</div>
</div>
<CheckoutCompleteDialog orgId={organization.id} />
</OnboardingLayout>
</main>
);
}
2 changes: 1 addition & 1 deletion apps/app/src/app/(app)/onboarding/[orgId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default async function OnboardingPage({ params }: OnboardingPageProps) {
Object.assign(initialData, {
describe:
initialData.describe ||
'comp ai is a grc platform saas that gets companies compliant with soc2 iso and hipaa in days',
'Bubba AI, Inc. is the company behind Comp AI - the fastest way to get SOC 2 compliant.',
industry: initialData.industry || 'SaaS',
teamSize: initialData.teamSize || '1-10',
devices: initialData.devices || 'Personal laptops',
Expand Down
Loading
Loading