Skip to content

Commit 442dc78

Browse files
refactor(integrations): improve layout and add SearchInput component (#1688)
Co-authored-by: Mariano Fuentes <marfuen98@gmail.com>
1 parent 768e695 commit 442dc78

File tree

3 files changed

+98
-72
lines changed

3 files changed

+98
-72
lines changed

apps/app/src/app/(app)/[orgId]/integrations/components/IntegrationsGrid.tsx

Lines changed: 61 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import {
1010
DialogHeader,
1111
DialogTitle,
1212
} from '@comp/ui/dialog';
13-
import { Input } from '@comp/ui/input';
14-
import { ArrowRight, Search, Sparkles, X } from 'lucide-react';
13+
import { ArrowRight, Sparkles } from 'lucide-react';
1514
import Image from 'next/image';
1615
import Link from 'next/link';
1716
import { useParams } from 'next/navigation';
@@ -23,6 +22,7 @@ import {
2322
type Integration,
2423
type IntegrationCategory,
2524
} from '../data/integrations';
25+
import { SearchInput } from './SearchInput';
2626

2727
const LOGO_TOKEN = 'pk_AZatYxV5QDSfWpRDaBxzRQ';
2828

@@ -63,27 +63,16 @@ export function IntegrationsGrid() {
6363
};
6464

6565
return (
66-
<div className="space-y-8">
66+
<div className="space-y-4">
6767
{/* Search and Filters */}
68-
<div className="space-y-4">
68+
<div className="flex items-center gap-2">
6969
{/* Search Bar */}
70-
<div className="relative">
71-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
72-
<Input
73-
placeholder="Search integrations..."
74-
value={searchQuery}
75-
onChange={(e) => setSearchQuery(e.target.value)}
76-
className="pl-10"
77-
/>
78-
{searchQuery && (
79-
<button
80-
onClick={() => setSearchQuery('')}
81-
className="absolute right-3 top-1/2 -translate-y-1/2"
82-
>
83-
<X className="w-4 h-4 text-muted-foreground hover:text-foreground" />
84-
</button>
85-
)}
86-
</div>
70+
<SearchInput
71+
value={searchQuery}
72+
onChange={setSearchQuery}
73+
placeholder="Search integrations..."
74+
className="w-80 flex-shrink-0"
75+
/>
8776

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

110-
{/* Results info - only show when filtering */}
111-
{(searchQuery || selectedCategory !== 'All') && filteredIntegrations.length > 0 && (
112-
<div className="text-sm text-muted-foreground">
113-
Showing {filteredIntegrations.length}{' '}
114-
{filteredIntegrations.length === 1 ? 'match' : 'matches'}
115-
</div>
116-
)}
117-
118-
{/* Integration Cards */}
119-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
120-
{filteredIntegrations.map((integration) => (
121-
<Card
122-
key={integration.id}
123-
className="group relative overflow-hidden hover:shadow-md transition-all hover:border-primary/30 cursor-pointer"
124-
onClick={() => setSelectedIntegration(integration)}
125-
>
126-
<CardHeader className="pb-3">
127-
<div className="flex items-start justify-between">
128-
<div className="flex items-center gap-3">
129-
<div className="w-12 h-12 rounded-xl bg-background border border-border flex items-center justify-center overflow-hidden">
130-
<Image
131-
src={`https://img.logo.dev/${integration.domain}?token=${LOGO_TOKEN}`}
132-
alt={`${integration.name} logo`}
133-
width={32}
134-
height={32}
135-
unoptimized
136-
className="object-contain rounded-md"
137-
/>
138-
</div>
139-
<div>
140-
<CardTitle className="text-base font-semibold flex items-center gap-2">
141-
{integration.name}
142-
{integration.popular && (
143-
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
144-
Popular
145-
</Badge>
146-
)}
147-
</CardTitle>
148-
<p className="text-xs text-muted-foreground mt-0.5">{integration.category}</p>
99+
<div className="space-y-2">
100+
{/* Integration Cards */}
101+
{(searchQuery || selectedCategory !== 'All') && filteredIntegrations.length > 0 && (
102+
<div className="text-sm text-muted-foreground">
103+
Showing {filteredIntegrations.length}{' '}
104+
{filteredIntegrations.length === 1 ? 'match' : 'matches'}
105+
</div>
106+
)}
107+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
108+
{/* Results info - only show when filtering */}
109+
{filteredIntegrations.map((integration) => (
110+
<Card
111+
key={integration.id}
112+
className="group relative overflow-hidden hover:shadow-md transition-all hover:border-primary/30 cursor-pointer"
113+
onClick={() => setSelectedIntegration(integration)}
114+
>
115+
<CardHeader className="pb-3">
116+
<div className="flex items-start justify-between">
117+
<div className="flex items-center gap-3">
118+
<div className="w-12 h-12 rounded-xl bg-background border border-border flex items-center justify-center overflow-hidden">
119+
<Image
120+
src={`https://img.logo.dev/${integration.domain}?token=${LOGO_TOKEN}`}
121+
alt={`${integration.name} logo`}
122+
width={32}
123+
height={32}
124+
unoptimized
125+
className="object-contain rounded-md"
126+
/>
127+
</div>
128+
<div>
129+
<CardTitle className="text-base font-semibold flex items-center gap-2">
130+
{integration.name}
131+
{integration.popular && (
132+
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
133+
Popular
134+
</Badge>
135+
)}
136+
</CardTitle>
137+
<p className="text-xs text-muted-foreground mt-0.5">{integration.category}</p>
138+
</div>
149139
</div>
150140
</div>
151-
</div>
152-
</CardHeader>
153-
<CardContent>
154-
<CardDescription className="text-sm leading-relaxed line-clamp-2">
155-
{integration.description}
156-
</CardDescription>
157-
</CardContent>
141+
</CardHeader>
142+
<CardContent>
143+
<CardDescription className="text-sm leading-relaxed line-clamp-2">
144+
{integration.description}
145+
</CardDescription>
146+
</CardContent>
158147

159-
{/* Hover overlay */}
160-
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
161-
</Card>
162-
))}
148+
{/* Hover overlay */}
149+
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
150+
</Card>
151+
))}
152+
</div>
163153
</div>
164154

165155
{/* Empty state - opportunity, not limitation */}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use client';
2+
3+
import { cn } from '@comp/ui/utils/cn';
4+
import { Search, X } from 'lucide-react';
5+
6+
interface SearchInputProps {
7+
value: string;
8+
onChange: (value: string) => void;
9+
placeholder?: string;
10+
className?: string;
11+
}
12+
13+
export function SearchInput({ value, onChange, placeholder, className }: SearchInputProps) {
14+
return (
15+
<div className={cn('relative w-full py-1 rounded-md border border-input', className)}>
16+
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
17+
<Search className="w-4 h-4 text-muted-foreground" />
18+
</div>
19+
<input
20+
value={value}
21+
onChange={(e) => onChange(e.target.value)}
22+
placeholder={placeholder}
23+
className="pl-10 pr-10"
24+
/>
25+
{value && (
26+
<button
27+
onClick={() => onChange('')}
28+
className="absolute inset-y-0 right-0 flex items-center pr-3"
29+
type="button"
30+
>
31+
<X className="w-4 h-4 text-muted-foreground hover:text-foreground transition-colors" />
32+
</button>
33+
)}
34+
</div>
35+
);
36+
}

apps/app/src/app/(app)/[orgId]/integrations/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default function IntegrationsPage() {
55
<div className="container mx-auto p-8">
66
<div className="max-w-7xl mx-auto space-y-10">
77
{/* Header */}
8-
<div className="space-y-4">
8+
<div className="space-y-2">
99
<div className="flex items-baseline gap-3">
1010
<h1 className="text-3xl font-bold tracking-tight">Integrations</h1>
1111
<span className="text-2xl text-muted-foreground/40 font-light"></span>

0 commit comments

Comments
 (0)