Skip to content

Commit 116ac38

Browse files
authored
Merge pull request #1946 from dubinc/integrations-updates
Integrations Updates
2 parents 1f7d65e + 920b2ff commit 116ac38

File tree

25 files changed

+933
-330
lines changed

25 files changed

+933
-330
lines changed

apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/loading.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { MaxWidthWrapper } from "@dub/ui";
22

33
export default function IntegrationPageLoading() {
44
return (
5-
<MaxWidthWrapper className="grid max-w-screen-lg gap-8">
5+
<MaxWidthWrapper className="grid max-w-screen-md gap-8">
66
<div className="h-4 w-28 rounded-full bg-gray-100" />
77
<div className="flex justify-between gap-2">
88
<div className="flex items-center gap-x-3">

apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/manage/page.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import AddEditIntegrationForm from "@/ui/oauth-apps/add-edit-integration-form";
2+
import { BackLink } from "@/ui/shared/back-link";
23
import { prisma } from "@dub/prisma";
34
import { MaxWidthWrapper } from "@dub/ui";
4-
import { ChevronLeft } from "lucide-react";
5-
import Link from "next/link";
65
import { notFound } from "next/navigation";
76

87
export default async function IntegrationManagePage({
@@ -25,15 +24,9 @@ export default async function IntegrationManagePage({
2524
}
2625
return (
2726
<MaxWidthWrapper className="grid max-w-screen-lg gap-8">
28-
<Link
29-
href={`/${params.slug}/settings/integrations`}
30-
className="flex items-center gap-x-1"
31-
>
32-
<ChevronLeft className="size-4" />
33-
<p className="text-sm font-medium text-gray-500">
34-
Back to integrations
35-
</p>
36-
</Link>
27+
<BackLink href={`/${params.slug}/settings/integrations`}>
28+
Back to integrations
29+
</BackLink>
3730

3831
<AddEditIntegrationForm
3932
integration={{

apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page-client.tsx

Lines changed: 109 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { SlackSettings } from "@/lib/integrations/slack/ui/settings";
66
import { ZapierSettings } from "@/lib/integrations/zapier/ui/settings";
77
import useWorkspace from "@/lib/swr/use-workspace";
88
import { InstalledIntegrationInfoProps } from "@/lib/types";
9+
import { IntegrationLogo } from "@/ui/integrations/integration-logo";
910
import { useUninstallIntegrationModal } from "@/ui/modals/uninstall-integration-modal";
11+
import { BackLink } from "@/ui/shared/back-link";
1012
import { ThreeDots } from "@/ui/shared/icons";
1113
import {
1214
Avatar,
@@ -17,28 +19,29 @@ import {
1719
CarouselContent,
1820
CarouselItem,
1921
CarouselNavBar,
22+
Logo,
2023
MaxWidthWrapper,
2124
Popover,
22-
TokenAvatar,
2325
Tooltip,
2426
TooltipContent,
2527
} from "@dub/ui";
2628
import {
2729
CircleWarning,
2830
ConnectedDots,
31+
DubCraftedShield,
2932
Globe,
3033
OfficeBuilding,
31-
ShieldCheck,
34+
Trash,
3235
} from "@dub/ui/icons";
3336
import {
3437
cn,
38+
DUB_WORKSPACE_ID,
3539
formatDate,
3640
getDomainWithoutWWW,
3741
SEGMENT_INTEGRATION_ID,
3842
SLACK_INTEGRATION_ID,
3943
ZAPIER_INTEGRATION_ID,
4044
} from "@dub/utils";
41-
import { BookOpenText, ChevronLeft, Trash } from "lucide-react";
4245
import { useAction } from "next-safe-action/hooks";
4346
import Link from "next/link";
4447
import { useState } from "react";
@@ -80,50 +83,38 @@ export default function IntegrationPageClient({
8083
const SettingsComponent = integrationSettings[integration.id] || null;
8184

8285
return (
83-
<MaxWidthWrapper className="grid max-w-screen-lg gap-8">
86+
<MaxWidthWrapper className="grid max-w-screen-lg grid-cols-1 gap-6">
8487
{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+
/>
10796
<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>
121110
<CircleWarning className="size-5 text-gray-500" invert />
122-
)}
123-
</div>
124-
</Tooltip>
111+
</div>
112+
</Tooltip>
113+
) : null}
125114
</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>
127118
</div>
128119
</div>
129120

@@ -133,7 +124,7 @@ export default function IntegrationPageClient({
133124
content={
134125
<div className="grid w-screen gap-px p-2 sm:w-48">
135126
<Button
136-
text="Uninstall Integration"
127+
text="Remove Integration"
137128
variant="danger-outline"
138129
icon={<Trash className="size-4" />}
139130
className="h-9 justify-start px-2"
@@ -160,55 +151,82 @@ export default function IntegrationPageClient({
160151
onClick={() => setOpenPopover(!openPopover)}
161152
className={cn(
162153
"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",
165156
)}
166157
>
167-
<ThreeDots className="h-5 w-5 text-gray-500" />
158+
<ThreeDots className="h-5 w-5 text-neutral-500" />
168159
</button>
169160
</Popover>
170161
)}
171162
</div>
172163

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}
188227
</div>
189228
</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+
))}
212230
</div>
213231

214232
<div className="flex items-center gap-x-2">
@@ -249,14 +267,12 @@ export default function IntegrationPageClient({
249267
</div>
250268
</div>
251269

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">
258271
{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+
>
260276
<CarouselContent>
261277
{integration.screenshots.map((src, idx) => (
262278
<CarouselItem key={idx}>
@@ -265,7 +281,7 @@ export default function IntegrationPageClient({
265281
alt={`Screenshot of ${integration.name}`}
266282
width={900}
267283
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"
269285
/>
270286
</CarouselItem>
271287
))}
@@ -279,7 +295,7 @@ export default function IntegrationPageClient({
279295
className={cn(
280296
"prose prose-sm prose-gray max-w-none p-6 transition-all",
281297
"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",
283299
)}
284300
components={{
285301
a: ({ node, ...props }) => (

apps/web/app/app.dub.co/(dashboard)/[slug]/settings/integrations/[integrationSlug]/page.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default async function IntegrationPage({
3030
select: {
3131
id: true,
3232
name: true,
33+
email: true,
3334
image: true,
3435
},
3536
},
@@ -58,25 +59,28 @@ export default async function IntegrationPage({
5859
: undefined;
5960

6061
return (
61-
<IntegrationPageClient
62-
integration={{
63-
...integration,
64-
screenshots: integration.screenshots as string[],
65-
installations: integration._count.installations,
66-
installed: installed
67-
? {
68-
id: integration.installations[0].id,
69-
by: {
70-
id: integration.installations[0].userId,
71-
name: integration.installations[0].user.name,
72-
image: integration.installations[0].user.image,
73-
},
74-
createdAt: integration.installations[0].createdAt,
75-
}
76-
: null,
77-
credentials,
78-
webhookId,
79-
}}
80-
/>
62+
<div className="mx-auto w-full max-w-screen-md">
63+
<IntegrationPageClient
64+
integration={{
65+
...integration,
66+
screenshots: integration.screenshots as string[],
67+
installations: integration._count.installations,
68+
installed: installed
69+
? {
70+
id: integration.installations[0].id,
71+
by: {
72+
id: integration.installations[0].userId,
73+
name: integration.installations[0].user.name,
74+
email: integration.installations[0].user.email,
75+
image: integration.installations[0].user.image,
76+
},
77+
createdAt: integration.installations[0].createdAt,
78+
}
79+
: null,
80+
credentials,
81+
webhookId,
82+
}}
83+
/>
84+
</div>
8185
);
8286
}

0 commit comments

Comments
 (0)