fix(dashboard): remove popup from slack integrations
This commit is contained in:
62
apps/api/src/controllers/error.html
Normal file
62
apps/api/src/controllers/error.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Error - OpenPanel</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f5f5f5;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
text-align: left;
|
||||
padding: 2rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<img src="https://openpanel.dev/logo.svg" alt="OpenPanel Logo" class="logo">
|
||||
<h1>Oops! Something went wrong</h1>
|
||||
<p>We encountered an error while processing your request. Please try again later or contact support if the problem
|
||||
persists.</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -224,39 +224,3 @@ export async function wsProjectNotifications(
|
||||
getRedisSub().off('message', message as any);
|
||||
});
|
||||
}
|
||||
|
||||
export async function wsIntegrationsSlack(
|
||||
connection: {
|
||||
socket: WebSocket;
|
||||
},
|
||||
req: FastifyRequest<{
|
||||
Querystring: {
|
||||
organizationId?: string;
|
||||
};
|
||||
}>,
|
||||
) {
|
||||
const { organizationId } = req.query;
|
||||
|
||||
if (!organizationId) {
|
||||
connection.socket.send('No organizationId provided');
|
||||
connection.socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const subscribeToEvent = 'integrations:slack';
|
||||
|
||||
getRedisSub().subscribe(subscribeToEvent);
|
||||
const onMessage = (channel: string, message: string) => {
|
||||
if (channel === subscribeToEvent) {
|
||||
const parsed = getSuperJson<{ organizationId: string }>(message);
|
||||
if (parsed && parsed.organizationId === organizationId) {
|
||||
connection.socket.send(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
getRedisSub().on('message', onMessage);
|
||||
connection.socket.on('close', () => {
|
||||
getRedisSub().unsubscribe(subscribeToEvent);
|
||||
getRedisSub().off('message', onMessage);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import type { WebhookEvent } from '@clerk/fastify';
|
||||
import { setSuperJson } from '@openpanel/common';
|
||||
import { AccessLevel, db } from '@openpanel/db';
|
||||
import {
|
||||
sendSlackNotification,
|
||||
@@ -167,6 +168,7 @@ const paramsSchema = z.object({
|
||||
|
||||
const metadataSchema = z.object({
|
||||
organizationId: z.string(),
|
||||
projectId: z.string(),
|
||||
integrationId: z.string(),
|
||||
});
|
||||
|
||||
@@ -179,7 +181,7 @@ export async function slackWebhook(
|
||||
const parsedParams = paramsSchema.safeParse(request.query);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
request.log.error('Invalid params', parsedParams);
|
||||
request.log.error(parsedParams.error, 'Invalid params');
|
||||
return reply.status(400).send({ error: 'Invalid params' });
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ export async function slackWebhook(
|
||||
);
|
||||
|
||||
if (!parsedMetadata.success) {
|
||||
request.log.error('Invalid metadata', parsedMetadata.error.errors);
|
||||
request.log.error(parsedMetadata.error, 'Invalid metadata');
|
||||
return reply.status(400).send({ error: 'Invalid metadata' });
|
||||
}
|
||||
|
||||
@@ -217,10 +219,8 @@ export async function slackWebhook(
|
||||
},
|
||||
'Failed to parse slack auth response',
|
||||
);
|
||||
return reply
|
||||
.status(400)
|
||||
.header('Content-Type', 'text/html')
|
||||
.send('<h1>Failed to exchange code for token</h1>');
|
||||
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
|
||||
return reply.status(500).header('Content-Type', 'text/html').send(html);
|
||||
}
|
||||
|
||||
// Send a notification first to confirm the connection
|
||||
@@ -230,10 +230,12 @@ export async function slackWebhook(
|
||||
'👋 Hello. You have successfully connected OpenPanel.dev to your Slack workspace.',
|
||||
});
|
||||
|
||||
const { projectId, organizationId, integrationId } = parsedMetadata.data;
|
||||
|
||||
await db.integration.update({
|
||||
where: {
|
||||
id: parsedMetadata.data.integrationId,
|
||||
organizationId: parsedMetadata.data.organizationId,
|
||||
id: integrationId,
|
||||
organizationId,
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
@@ -243,22 +245,12 @@ export async function slackWebhook(
|
||||
},
|
||||
});
|
||||
|
||||
getRedisPub().publish(
|
||||
'integrations:slack',
|
||||
setSuperJson({
|
||||
organizationId: parsedMetadata.data.organizationId,
|
||||
}),
|
||||
return reply.redirect(
|
||||
`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/${organizationId}/${projectId}/settings/integrations?tab=installed`,
|
||||
);
|
||||
|
||||
return reply
|
||||
.status(200)
|
||||
.header('Content-Type', 'text/html')
|
||||
.send('<h1>Slack integration added. You can close this window now.</h1>');
|
||||
} catch (err) {
|
||||
request.log.error(err);
|
||||
return reply
|
||||
.status(500)
|
||||
.header('Content-Type', 'text/html')
|
||||
.send('<h1>Failed to exchange code for token</h1>');
|
||||
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
|
||||
return reply.status(500).header('Content-Type', 'text/html').send(html);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +32,6 @@ const liveRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||
{ websocket: true },
|
||||
controller.wsProjectNotifications,
|
||||
);
|
||||
fastify.get(
|
||||
'/integrations/slack',
|
||||
{ websocket: true },
|
||||
controller.wsIntegrationsSlack,
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
@@ -19,49 +19,20 @@ export function SlackIntegrationForm({
|
||||
defaultValues?: RouterOutputs['integration']['get'];
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const { organizationId } = useAppParams();
|
||||
useWS(`/live/integrations/slack?organizationId=${organizationId}`, (res) => {
|
||||
// @ts-expect-error
|
||||
console.log('3. slack integration done', window.slackPopup);
|
||||
// @ts-expect-error
|
||||
if (window.slackPopup && typeof window.slackPopup.close === 'function') {
|
||||
console.log('4. close popup');
|
||||
// @ts-expect-error
|
||||
window.slackPopup.close();
|
||||
}
|
||||
onSuccess();
|
||||
});
|
||||
const { organizationId, projectId } = useAppParams();
|
||||
|
||||
const form = useForm<IForm>({
|
||||
defaultValues: {
|
||||
id: defaultValues?.id,
|
||||
organizationId,
|
||||
projectId,
|
||||
name: defaultValues?.name ?? '',
|
||||
},
|
||||
resolver: zodResolver(zCreateSlackIntegration),
|
||||
});
|
||||
const mutation = api.integration.createOrUpdateSlack.useMutation({
|
||||
async onSuccess(res) {
|
||||
console.log('1. onSuccess', res);
|
||||
|
||||
const url = res.slackInstallUrl;
|
||||
const width = 600;
|
||||
const height = 800;
|
||||
const left = window.screenX + (window.outerWidth - width) / 2;
|
||||
const top = window.screenY + (window.outerHeight - height) / 2.5;
|
||||
console.log('2. open popup');
|
||||
// @ts-expect-error
|
||||
window.slackPopup = window.open(
|
||||
url,
|
||||
'',
|
||||
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}`,
|
||||
);
|
||||
|
||||
// The popup might have been blocked, so we redirect the user to the URL instead
|
||||
//
|
||||
// @ts-expect-error
|
||||
if (!window.slackPopup) {
|
||||
window.location.href = url;
|
||||
}
|
||||
window.location.href = res.slackInstallUrl;
|
||||
},
|
||||
onError() {
|
||||
toast.error('Failed to create integration');
|
||||
|
||||
@@ -18,7 +18,8 @@ export const slackInstaller = new InstallProvider({
|
||||
export const getSlackInstallUrl = ({
|
||||
integrationId,
|
||||
organizationId,
|
||||
}: { integrationId: string; organizationId: string }) => {
|
||||
projectId,
|
||||
}: { integrationId: string; organizationId: string; projectId: string }) => {
|
||||
return slackInstaller.generateInstallUrl({
|
||||
scopes: [
|
||||
'incoming-webhook',
|
||||
@@ -27,7 +28,7 @@ export const getSlackInstallUrl = ({
|
||||
'team:read',
|
||||
],
|
||||
redirectUri: SLACK_OAUTH_REDIRECT_URL,
|
||||
metadata: JSON.stringify({ integrationId, organizationId }),
|
||||
metadata: JSON.stringify({ integrationId, organizationId, projectId }),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
slackInstallUrl: await getSlackInstallUrl({
|
||||
integrationId: res.id,
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -84,6 +85,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
slackInstallUrl: await getSlackInstallUrl({
|
||||
integrationId: res.id,
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -214,6 +214,7 @@ const zCreateIntegration = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1),
|
||||
organizationId: z.string().min(1),
|
||||
projectId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const zCreateSlackIntegration = zCreateIntegration;
|
||||
|
||||
Reference in New Issue
Block a user