@@ -6,7 +6,9 @@ import { SlackSettings } from "@/lib/integrations/slack/ui/settings";
6
6
import { ZapierSettings } from "@/lib/integrations/zapier/ui/settings" ;
7
7
import useWorkspace from "@/lib/swr/use-workspace" ;
8
8
import { InstalledIntegrationInfoProps } from "@/lib/types" ;
9
+ import { IntegrationLogo } from "@/ui/integrations/integration-logo" ;
9
10
import { useUninstallIntegrationModal } from "@/ui/modals/uninstall-integration-modal" ;
11
+ import { BackLink } from "@/ui/shared/back-link" ;
10
12
import { ThreeDots } from "@/ui/shared/icons" ;
11
13
import {
12
14
Avatar ,
@@ -17,28 +19,29 @@ import {
17
19
CarouselContent ,
18
20
CarouselItem ,
19
21
CarouselNavBar ,
22
+ Logo ,
20
23
MaxWidthWrapper ,
21
24
Popover ,
22
- TokenAvatar ,
23
25
Tooltip ,
24
26
TooltipContent ,
25
27
} from "@dub/ui" ;
26
28
import {
27
29
CircleWarning ,
28
30
ConnectedDots ,
31
+ DubCraftedShield ,
29
32
Globe ,
30
33
OfficeBuilding ,
31
- ShieldCheck ,
34
+ Trash ,
32
35
} from "@dub/ui/icons" ;
33
36
import {
34
37
cn ,
38
+ DUB_WORKSPACE_ID ,
35
39
formatDate ,
36
40
getDomainWithoutWWW ,
37
41
SEGMENT_INTEGRATION_ID ,
38
42
SLACK_INTEGRATION_ID ,
39
43
ZAPIER_INTEGRATION_ID ,
40
44
} from "@dub/utils" ;
41
- import { BookOpenText , ChevronLeft , Trash } from "lucide-react" ;
42
45
import { useAction } from "next-safe-action/hooks" ;
43
46
import Link from "next/link" ;
44
47
import { useState } from "react" ;
@@ -80,50 +83,38 @@ export default function IntegrationPageClient({
80
83
const SettingsComponent = integrationSettings [ integration . id ] || null ;
81
84
82
85
return (
83
- < MaxWidthWrapper className = "grid max-w-screen-lg gap-8 " >
86
+ < MaxWidthWrapper className = "grid max-w-screen-lg grid-cols-1 gap-6 " >
84
87
{ integration . installed && < UninstallIntegrationModal /> }
85
- < Link
86
- href = { `/${ slug } /settings/integrations` }
87
- className = "flex items-center gap-x-1"
88
- >
89
- < ChevronLeft className = "size-4" />
90
- < p className = "text-sm font-medium text-gray-500" > Integrations</ p >
91
- </ Link >
92
- < div className = "flex justify-between gap-2" >
93
- < div className = "flex items-center gap-x-3" >
94
- < div className = "flex-none rounded-md border border-gray-200 bg-gradient-to-t from-gray-100 p-2" >
95
- { integration . logo ? (
96
- < BlurImage
97
- src = { integration . logo }
98
- alt = { `Logo for ${ integration . name } ` }
99
- className = "size-8 rounded-full border border-gray-200"
100
- width = { 20 }
101
- height = { 20 }
102
- />
103
- ) : (
104
- < TokenAvatar id = { integration . id } className = "size-8" />
105
- ) }
106
- </ div >
88
+ < BackLink href = { `/${ slug } /settings/integrations` } > Integrations</ BackLink >
89
+ < div className = "flex justify-between gap-8" >
90
+ < div className = "flex flex-col gap-3 sm:flex-row sm:items-center" >
91
+ < IntegrationLogo
92
+ src = { integration . logo ?? null }
93
+ alt = { `Logo for ${ integration . name } ` }
94
+ className = "size-10 sm:size-14 sm:rounded-lg"
95
+ />
107
96
< div >
108
- < div className = "flex items-center gap-1" >
109
- < p className = "font-semibold text-gray-700" > { integration . name } </ p >
110
- < Tooltip
111
- content = {
112
- integration . verified
113
- ? "This is a verified integration."
114
- : "Dub hasn't verified this integration. Install it at your own risk."
115
- }
116
- >
117
- < div >
118
- { integration . verified ? (
119
- < ShieldCheck className = "size-5 text-[#E2B719]" invert / >
120
- ) : (
97
+ < div className = "flex items-center gap-1.5 " >
98
+ < h1 className = "text-base font-semibold leading-none text-neutral-800" >
99
+ { integration . name }
100
+ </ h1 >
101
+ { integration . projectId === DUB_WORKSPACE_ID ? (
102
+ < Tooltip content = "This is an official integration built and maintained by Dub" >
103
+ < div >
104
+ < DubCraftedShield className = "size-4 -translate-y-px" />
105
+ </ div >
106
+ </ Tooltip >
107
+ ) : ! integration . verified ? (
108
+ < Tooltip content = "Dub hasn't verified this integration. Install it at your own risk." >
109
+ < div >
121
110
< CircleWarning className = "size-5 text-gray-500" invert />
122
- ) }
123
- </ div >
124
- </ Tooltip >
111
+ </ div >
112
+ </ Tooltip >
113
+ ) : null }
125
114
</ div >
126
- < p className = "text-sm text-gray-500" > { integration . description } </ p >
115
+ < p className = "mt-1 text-[0.8125rem] leading-snug text-neutral-600" >
116
+ { integration . description }
117
+ </ p >
127
118
</ div >
128
119
</ div >
129
120
@@ -133,7 +124,7 @@ export default function IntegrationPageClient({
133
124
content = {
134
125
< div className = "grid w-screen gap-px p-2 sm:w-48" >
135
126
< Button
136
- text = "Uninstall Integration"
127
+ text = "Remove Integration"
137
128
variant = "danger-outline"
138
129
icon = { < Trash className = "size-4" /> }
139
130
className = "h-9 justify-start px-2"
@@ -160,55 +151,82 @@ export default function IntegrationPageClient({
160
151
onClick = { ( ) => setOpenPopover ( ! openPopover ) }
161
152
className = { cn (
162
153
"flex h-10 items-center rounded-md border px-1.5 outline-none transition-all" ,
163
- "border-gray -200 bg-white text-gray -900 placeholder-gray -400" ,
164
- "focus-visible:border-gray -500 data-[state=open]:border-gray -500 data-[state=open]:ring-4 data-[state=open]:ring-gray -200" ,
154
+ "border-neutral -200 bg-white text-neutral -900 placeholder-neutral -400" ,
155
+ "focus-visible:border-neutral -500 data-[state=open]:border-neutral -500 data-[state=open]:ring-4 data-[state=open]:ring-neutral -200" ,
165
156
) }
166
157
>
167
- < ThreeDots className = "h-5 w-5 text-gray -500" />
158
+ < ThreeDots className = "h-5 w-5 text-neutral -500" />
168
159
</ button >
169
160
</ Popover >
170
161
) }
171
162
</ div >
172
163
173
- < div className = "flex flex-col justify-between gap-4 rounded-lg border border-gray-200 bg-white p-4 sm:flex-row sm:gap-0" >
174
- < div className = "flex flex-col gap-4 sm:flex-row sm:gap-12" >
175
- { integration . installed && (
176
- < div className = "flex items-center gap-2" >
177
- < Avatar user = { integration . installed . by } className = "size-8" />
178
- < div className = "flex flex-col gap-1" >
179
- < p className = "text-xs text-gray-500" > INSTALLED BY</ p >
180
- < p className = "text-sm font-medium text-gray-700" >
181
- { integration . installed . by . name }
182
- < span className = "ml-1 font-normal text-gray-500" >
183
- { formatDate ( integration . installed . createdAt , {
184
- year : undefined ,
185
- } ) }
186
- </ span >
187
- </ p >
164
+ < div className = "flex flex-col justify-between gap-4 rounded-lg border border-neutral-200 bg-white p-4 sm:flex-row sm:gap-0" >
165
+ < div className = "flex flex-col gap-4 sm:flex-row sm:gap-8" >
166
+ { [
167
+ ...( integration . installed
168
+ ? [
169
+ {
170
+ label : "Enabled by" ,
171
+ content : (
172
+ < span className = "text-neutral-700" >
173
+ < Avatar
174
+ user = { integration . installed . by }
175
+ className = "inline-block size-3 -translate-y-0.5 border-0"
176
+ /> { " " }
177
+ { integration . installed . by . name }
178
+ < span className = "ml-1 font-normal text-neutral-600" >
179
+ { formatDate ( integration . installed . createdAt , {
180
+ month : "short" ,
181
+ year :
182
+ integration . installed . createdAt . getFullYear ( ) ===
183
+ new Date ( ) . getFullYear ( )
184
+ ? undefined
185
+ : "numeric" ,
186
+ } ) }
187
+ </ span >
188
+ </ span >
189
+ ) ,
190
+ } ,
191
+ ]
192
+ : [ ] ) ,
193
+ {
194
+ label : "Built by" ,
195
+ content : (
196
+ < div className = "flex items-center gap-1.5 text-sm font-medium text-neutral-700" >
197
+ { integration . projectId === DUB_WORKSPACE_ID ? (
198
+ < Logo className = "size-3.5" />
199
+ ) : (
200
+ < OfficeBuilding className = "size-3.5" />
201
+ ) }
202
+ { integration . developer }
203
+ </ div >
204
+ ) ,
205
+ } ,
206
+ {
207
+ label : "Website" ,
208
+ content : (
209
+ < a
210
+ href = { integration . website }
211
+ className = "flex items-center gap-1.5 text-sm text-neutral-700 transition-colors duration-100 hover:text-neutral-900"
212
+ target = "_blank"
213
+ rel = "noopener noreferrer"
214
+ >
215
+ < Globe className = "size-3.5" />
216
+ { getDomainWithoutWWW ( integration . website ) }
217
+ </ a >
218
+ ) ,
219
+ } ,
220
+ ] . map ( ( { label, content } ) => (
221
+ < div className = "flex flex-col gap-1" >
222
+ < span className = "text-xs uppercase text-neutral-500" >
223
+ { label }
224
+ </ span >
225
+ < div className = "text-[0.8125rem] font-medium text-neutral-600" >
226
+ { content }
188
227
</ div >
189
228
</ div >
190
- ) }
191
-
192
- < div className = "flex flex-col gap-1" >
193
- < p className = "text-xs text-gray-500" > DEVELOPER</ p >
194
- < div className = "flex items-center gap-x-1 text-sm font-medium text-gray-700" >
195
- < OfficeBuilding className = "size-3" />
196
- { integration . developer }
197
- </ div >
198
- </ div >
199
-
200
- < div className = "flex flex-col gap-1" >
201
- < p className = "text-xs text-gray-500" > WEBSITE</ p >
202
- < a
203
- href = { integration . website }
204
- className = "flex items-center gap-x-1 text-sm font-medium text-gray-700"
205
- target = "_blank"
206
- rel = "noopener noreferrer"
207
- >
208
- < Globe className = "size-3" />
209
- { getDomainWithoutWWW ( integration . website ) }
210
- </ a >
211
- </ div >
229
+ ) ) }
212
230
</ div >
213
231
214
232
< div className = "flex items-center gap-x-2" >
@@ -249,14 +267,12 @@ export default function IntegrationPageClient({
249
267
</ div >
250
268
</ div >
251
269
252
- < div className = "w-full rounded-lg border border-gray-200 bg-white" >
253
- < div className = "flex items-center gap-x-2 border-b border-gray-200 px-6 py-4" >
254
- < BookOpenText className = "size-4" />
255
- < p className = "text-sm font-medium text-gray-700" > Overview</ p >
256
- </ div >
257
-
270
+ < div className = "w-full rounded-lg border border-neutral-200 bg-white" >
258
271
{ integration . screenshots && integration . screenshots . length > 0 ? (
259
- < Carousel autoplay = { { delay : 5000 } } className = "bg-white p-8" >
272
+ < Carousel
273
+ autoplay = { { delay : 5000 } }
274
+ className = "rounded-t-lg bg-white p-4"
275
+ >
260
276
< CarouselContent >
261
277
{ integration . screenshots . map ( ( src , idx ) => (
262
278
< CarouselItem key = { idx } >
@@ -265,7 +281,7 @@ export default function IntegrationPageClient({
265
281
alt = { `Screenshot of ${ integration . name } ` }
266
282
width = { 900 }
267
283
height = { 580 }
268
- className = "aspect-[900/580] w-[5/6] overflow-hidden rounded-md border border-gray -200 object-cover object-top"
284
+ className = "aspect-[900/580] w-[5/6] overflow-hidden rounded-md border border-neutral -200 object-cover object-top"
269
285
/>
270
286
</ CarouselItem >
271
287
) ) }
@@ -279,7 +295,7 @@ export default function IntegrationPageClient({
279
295
className = { cn (
280
296
"prose prose-sm prose-gray max-w-none p-6 transition-all" ,
281
297
"prose-headings:leading-tight" ,
282
- "prose-a:font-medium prose-a:text-gray -500 prose-a:underline-offset-4 hover:prose-a:text-black" ,
298
+ "prose-a:font-medium prose-a:text-neutral -500 prose-a:underline-offset-4 hover:prose-a:text-black" ,
283
299
) }
284
300
components = { {
285
301
a : ( { node, ...props } ) => (
0 commit comments