Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
70 changes: 54 additions & 16 deletions app/api/views-dataroom/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { generateOTP } from "@/lib/utils/generate-otp";
import { LOCALHOST_IP } from "@/lib/utils/geo";
import { checkGlobalBlockList } from "@/lib/utils/global-block-list";
import { validateEmail } from "@/lib/utils/validate-email";
import { notifyDataroomAccess, notifyDocumentView } from "@/lib/slack/events";

export async function POST(request: NextRequest) {
try {
Expand Down Expand Up @@ -603,28 +604,28 @@ export async function POST(request: NextRequest) {
...(link.enableAgreement &&
link.agreementId &&
hasConfirmedAgreement && {
agreementResponse: {
create: {
agreementId: link.agreementId,
},
agreementResponse: {
create: {
agreementId: link.agreementId,
},
}),
},
}),
...(link.audienceType === LinkAudienceType.GROUP &&
link.groupId && {
groupId: link.groupId,
}),
groupId: link.groupId,
}),
...(customFields &&
link.customFields.length > 0 && {
customFieldResponse: {
create: {
data: link.customFields.map((field) => ({
identifier: field.identifier,
label: field.label,
response: customFields[field.identifier] || "",
})),
},
customFieldResponse: {
create: {
data: link.customFields.map((field) => ({
identifier: field.identifier,
label: field.label,
response: customFields[field.identifier] || "",
})),
},
}),
},
}),
};

// ** DATAROOM_VIEW **
Expand Down Expand Up @@ -657,6 +658,24 @@ export async function POST(request: NextRequest) {
enableNotification: link.enableNotification,
}),
);

if (link.teamId && !isPreview) {
waitUntil(
(async () => {
try {
await notifyDataroomAccess({
teamId: link.teamId!,
dataroomId,
linkId,
viewerEmail: verifiedEmail ?? email,
viewerId: viewer?.id,
});
} catch (error) {
console.error("Error sending Slack notification:", error);
}
})()
);
}
}

const dataroomViewId =
Expand Down Expand Up @@ -768,6 +787,25 @@ export async function POST(request: NextRequest) {
select: { id: true },
});
console.timeEnd("create-view");
// Only send Slack notifications for non-preview views
if (link.teamId && !isPreview) {
waitUntil(
(async () => {
try {
await notifyDocumentView({
teamId: link.teamId!,
documentId,
dataroomId,
linkId,
viewerEmail: verifiedEmail ?? email,
viewerId: viewer?.id,
});
} catch (error) {
console.error("Error sending Slack notification:", error);
}
})()
);
}
}

// if document version has pages, then return pages
Expand Down
14 changes: 14 additions & 0 deletions app/api/views/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import prisma from "@/lib/prisma";
import { ratelimit } from "@/lib/redis";
import { parseSheet } from "@/lib/sheet";
import { recordLinkView } from "@/lib/tracking/record-link-view";
import { notifyDocumentView } from "@/lib/slack/events";
import { CustomUser, WatermarkConfigSchema } from "@/lib/types";
import { checkPassword, decryptEncrpytedPassword, log } from "@/lib/utils";
import { isEmailMatched } from "@/lib/utils/email-domain";
Expand Down Expand Up @@ -636,6 +637,19 @@ export async function POST(request: NextRequest) {
enableNotification: link.enableNotification,
}),
);
if (!isPreview) {
waitUntil(
notifyDocumentView({
teamId: link.teamId!,
documentId,
linkId,
viewerEmail: email ?? undefined,
viewerId: viewer?.id ?? undefined,
}).catch((error) => {
console.error("Error sending Slack notification:", error);
})
);
}
}

const returnObject = {
Expand Down
85 changes: 85 additions & 0 deletions components/emails/slack-integration-notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from "react";

import {
Body,
Button,
Container,
Head,
Hr,
Html,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";

export default function SlackIntegrationNotification({
teamName,
userEmail,
settingsUrl,
}: {
teamName: string;
userEmail: string;
settingsUrl: string;
}) {
return (
<Html>
<Head />
<Preview>Slack integration has been added to your workspace</Preview>
<Tailwind>
<Body className="mx-auto my-auto bg-white font-sans">
<Container className="mx-auto my-10 w-[465px] p-5">
<Text className="mx-0 mb-8 mt-4 p-0 text-center text-2xl font-normal">
<span className="font-bold tracking-tighter">Papermark</span>
</Text>
<Text className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
An integration has been added to your workspace
</Text>
<Text className="text-sm leading-6 text-black">
The Slack integration has been added to your workspace{" "}
<span className="font-semibold">{teamName}</span> on{" "}
<span className="font-semibold">Papermark</span>.
</Text>
<Text className="text-sm leading-6 text-black">
You can now receive notifications about document views, dataroom
access and downloads directly in your Slack channels.
</Text>
<Section className="my-8 text-center">
<Button
className="rounded bg-black text-center text-xs font-semibold text-white no-underline"
href={settingsUrl}
style={{ padding: "12px 20px" }}
>
View installed integration
</Button>
</Section>
<Text className="text-sm leading-6 text-black">
or copy and paste this URL into your browser:
</Text>
<Text className="max-w-sm flex-wrap break-words font-medium text-purple-600 no-underline">
{settingsUrl.replace(/^https?:\/\//, "")}
</Text>
<Hr />
<Section className="mt-8 text-gray-400">
<Text className="text-xs">
© {new Date().getFullYear()}{" "}
<a
href="https://www.papermark.com"
className="text-gray-400 no-underline hover:text-gray-400"
target="_blank"
rel="noopener noreferrer"
>
papermark.com
</a>
</Text>
<Text className="text-xs">
If you have any feedback or questions about this email, simply
reply to it. I&apos;d love to hear from you!
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
}
2 changes: 2 additions & 0 deletions components/layouts/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ const SettingsBreadcrumb = () => {
return "API Tokens";
case "/settings/webhooks":
return "Webhooks";
case "/settings/slack":
return "Slack";
case "/settings/incoming-webhooks":
return "Incoming Webhooks";
case "/settings/branding":
Expand Down
5 changes: 5 additions & 0 deletions components/settings/settings-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export function SettingsHeader() {
href: `/settings/webhooks`,
segment: "webhooks",
},
{
label: "Slack",
href: `/settings/slack`,
segment: "slack",
},
{
label: "Tokens",
href: `/settings/tokens`,
Expand Down
82 changes: 82 additions & 0 deletions components/settings/slack-settings-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Skeleton } from "@/components/ui/skeleton";

export default function SlackSettingsSkeleton() {
return (
<div className="space-y-6">
{/* Header Skeleton */}
<div className="mb-4 flex items-center justify-between md:mb-8 lg:mb-12">
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-80" />
</div>
<Skeleton className="h-10 w-32" />
</div>

{/* Main Content Skeleton */}
<div className="space-y-6">
{/* Card 1 */}
<div className="rounded-lg border bg-card p-6">
<div className="space-y-4">
<div className="space-y-2">
<Skeleton className="h-6 w-40" />
<Skeleton className="h-4 w-60" />
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-48" />
</div>
<Skeleton className="h-6 w-11" />
</div>
<div className="space-y-3">
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-64" />
</div>
<div className="flex items-center rounded-lg border border-border bg-background">
<div className="flex flex-1 items-center gap-2 px-2">
<Skeleton className="h-6 w-20" />
<Skeleton className="h-6 w-24" />
</div>
<Skeleton className="h-6 w-px" />
<Skeleton className="h-8 w-32 shrink-0" />
</div>
</div>
</div>
</div>
</div>

{/* Card 2 */}
<div className="rounded-lg border bg-card p-6">
<div className="space-y-4">
<div className="space-y-2">
<Skeleton className="h-6 w-44" />
<Skeleton className="h-4 w-72" />
</div>
<div className="space-y-4">
<div className="flex space-x-6">
{[1, 2, 3].map((i) => (
<div key={i} className="flex items-center space-x-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-16" />
</div>
))}
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Skeleton className="h-4 w-12" />
<Skeleton className="h-10 w-full" />
</div>
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
36 changes: 36 additions & 0 deletions components/shared/icons/slack-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";

interface SlackIconProps {
className?: string;
size?: number;
}

export function SlackIcon({ className = "", size = 24 }: SlackIconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
className={className}
>
{/* Slack logo with proper colors */}
<path
d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
fill="#E01E5A"
/>
<path
d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.527 2.527 0 0 1 8.834 0a2.527 2.527 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.52 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.833a2.528 2.528 0 0 1 2.522-2.52h6.312z"
fill="#36C5F0"
/>
<path
d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.527 2.527 0 0 1 24 8.834a2.527 2.527 0 0 1-2.522 2.52h-2.522V8.834zM17.688 8.834a2.527 2.527 0 0 1-2.52 2.52 2.526 2.526 0 0 1-2.52-2.52V2.522A2.527 2.527 0 0 1 15.166 0a2.527 2.527 0 0 1 2.522 2.522v6.312z"
fill="#2EB67D"
/>
<path
d="M15.166 18.956a2.528 2.528 0 0 1 2.52 2.522A2.527 2.527 0 0 1 15.166 24a2.527 2.527 0 0 1-2.52-2.522v-2.52h2.52zM15.166 17.688a2.527 2.527 0 0 1-2.52-2.52 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.168a2.528 2.528 0 0 1-2.522 2.52h-6.312z"
fill="#ECB22E"
/>
</svg>
);
}
5 changes: 5 additions & 0 deletions components/sidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
url: "/settings/webhooks",
current: router.pathname.includes("settings/webhooks"),
},
{
title: "Slack",
url: "/settings/slack",
current: router.pathname.includes("settings/slack"),
},
{
title: "Billing",
url: "/settings/billing",
Expand Down
Loading