Skip to content

Commit 613ace5

Browse files
Adding QOL features
1 parent d89fb11 commit 613ace5

4 files changed

Lines changed: 357 additions & 11 deletions

File tree

src/coffeeChats/coffeeChatActions.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { App, BlockAction, SlackActionMiddlewareArgs } from "@slack/bolt";
2-
import { optOutOfCoffeeChats, optInToCoffeeChats } from "./coffeeChatService";
2+
import {
3+
optOutOfCoffeeChats,
4+
optInToCoffeeChats,
5+
confirmMeetup,
6+
skipNextPairing,
7+
} from "./coffeeChatService";
38

49
export function registerCoffeeChatActions(slackbot: App) {
510
// Action handler for opting out of coffee chats
@@ -86,4 +91,73 @@ export function registerCoffeeChatActions(slackbot: App) {
8691
}
8792
},
8893
);
94+
95+
// Action handler for confirming meetup
96+
slackbot.action(
97+
"coffee_chat_confirm_meetup",
98+
async ({ ack, body, respond }: SlackActionMiddlewareArgs<BlockAction>) => {
99+
await ack();
100+
101+
try {
102+
const action = body.actions[0];
103+
const pairingId = ("value" in action ? action.value : "") as string;
104+
105+
await confirmMeetup(pairingId);
106+
107+
await respond({
108+
text: `Thanks for confirming! 🎉`,
109+
blocks: [
110+
{
111+
type: "section",
112+
text: {
113+
type: "mrkdwn",
114+
text: `✅ Awesome! Thanks for confirming your meetup. We hope you had a great time! 🎉`,
115+
},
116+
},
117+
],
118+
replace_original: false,
119+
});
120+
} catch (error) {
121+
await respond({
122+
text: `❌ Error confirming meetup: ${error}`,
123+
replace_original: false,
124+
});
125+
}
126+
},
127+
);
128+
129+
// Action handler for skipping the next pairing
130+
slackbot.action(
131+
"coffee_chat_skip_next",
132+
async ({ ack, body, respond }: SlackActionMiddlewareArgs<BlockAction>) => {
133+
await ack();
134+
135+
try {
136+
const userId = body.user.id;
137+
const action = body.actions[0];
138+
const channelId = ("value" in action ? action.value : "") as string;
139+
140+
await skipNextPairing(userId, channelId);
141+
142+
await respond({
143+
text: `You'll skip the next pairing.`,
144+
blocks: [
145+
{
146+
type: "section",
147+
text: {
148+
type: "mrkdwn",
149+
text: `✅ Got it! You'll skip the next coffee chat pairing. You'll automatically be included in the round after that.`,
150+
},
151+
},
152+
],
153+
replace_original: false,
154+
});
155+
} catch (error) {
156+
await respond({
157+
text: `❌ Error skipping next pairing: ${error}`,
158+
replace_original: false,
159+
});
160+
}
161+
},
162+
);
89163
}

src/coffeeChats/coffeeChatCommands.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { App } from "@slack/bolt";
2+
import moment from "moment-timezone";
23
import {
34
processCoffeeChatChannel,
45
registerCoffeeChatChannel,
@@ -10,11 +11,40 @@ import {
1011
CoffeeChatUserPreferenceModel,
1112
} from "./coffeeChatModels";
1213

14+
/**
15+
* Checks if a user is a workspace admin or owner
16+
*/
17+
const isUserAdmin = async (
18+
slackbot: App,
19+
userId: string,
20+
): Promise<boolean> => {
21+
try {
22+
const userInfo = await slackbot.client.users.info({ user: userId });
23+
return (
24+
userInfo.user?.is_admin ||
25+
userInfo.user?.is_owner ||
26+
userInfo.user?.is_primary_owner ||
27+
false
28+
);
29+
} catch {
30+
return false;
31+
}
32+
};
33+
1334
export function registerCoffeeChatCommands(slackbot: App) {
1435
// Command to register a channel for coffee chats
1536
slackbot.command("/register-coffee-chats", async ({ command, ack, say }) => {
1637
await ack();
1738

39+
// Check if user is admin
40+
const isAdmin = await isUserAdmin(slackbot, command.user_id);
41+
if (!isAdmin) {
42+
await say(
43+
`❌ Only workspace admins can register channels for coffee chats.`,
44+
);
45+
return;
46+
}
47+
1848
try {
1949
const channelId = command.channel_id;
2050
const channelInfo = await slackbot.client.conversations.info({
@@ -37,6 +67,15 @@ export function registerCoffeeChatCommands(slackbot: App) {
3767
slackbot.command("/trigger-coffee-chats", async ({ command, ack, say }) => {
3868
await ack();
3969

70+
// Check if user is admin
71+
const isAdmin = await isUserAdmin(slackbot, command.user_id);
72+
if (!isAdmin) {
73+
await say(
74+
`❌ Only workspace admins can manually trigger coffee chats.`,
75+
);
76+
return;
77+
}
78+
4079
try {
4180
const channelId = command.channel_id;
4281
const config = await CoffeeChatConfigModel.findOne({ channelId });
@@ -58,6 +97,13 @@ export function registerCoffeeChatCommands(slackbot: App) {
5897
slackbot.command("/disable-coffee-chats", async ({ command, ack, say }) => {
5998
await ack();
6099

100+
// Check if user is admin
101+
const isAdmin = await isUserAdmin(slackbot, command.user_id);
102+
if (!isAdmin) {
103+
await say(`❌ Only workspace admins can disable coffee chats.`);
104+
return;
105+
}
106+
61107
try {
62108
const channelId = command.channel_id;
63109
const result = await CoffeeChatConfigModel.updateOne(
@@ -161,6 +207,13 @@ export function registerCoffeeChatCommands(slackbot: App) {
161207
slackbot.command("/reset-coffee-chats", async ({ command, ack, say }) => {
162208
await ack();
163209

210+
// Check if user is admin
211+
const isAdmin = await isUserAdmin(slackbot, command.user_id);
212+
if (!isAdmin) {
213+
await say(`❌ Only workspace admins can reset coffee chats.`);
214+
return;
215+
}
216+
164217
try {
165218
const channelId = command.channel_id;
166219

@@ -205,4 +258,102 @@ export function registerCoffeeChatCommands(slackbot: App) {
205258
await say(`❌ Error resetting coffee chats: ${error}`);
206259
}
207260
});
261+
262+
// Command to view pairing history
263+
slackbot.command("/my-coffee-chats", async ({ command, ack, say }) => {
264+
await ack();
265+
266+
try {
267+
const userId = command.user_id;
268+
const channelId = command.channel_id;
269+
270+
// Check if channel is registered for coffee chats
271+
const config = await CoffeeChatConfigModel.findOne({ channelId });
272+
if (!config) {
273+
await say({
274+
text: `❌ This channel is not registered for coffee chats.`,
275+
});
276+
return;
277+
}
278+
279+
// Find all pairings that include this user in this channel
280+
const pairings = await CoffeeChatPairingModel.find({
281+
channelId,
282+
userIds: userId,
283+
}).sort({ createdAt: -1 });
284+
285+
if (pairings.length === 0) {
286+
await say({
287+
text: `You haven't been paired with anyone yet in this channel.`,
288+
blocks: [
289+
{
290+
type: "section",
291+
text: {
292+
type: "mrkdwn",
293+
text: `☕ You haven't been paired with anyone yet in this channel. Stay tuned for your first coffee chat!`,
294+
},
295+
},
296+
],
297+
});
298+
return;
299+
}
300+
301+
// Build the history message
302+
const historyLines: string[] = [];
303+
304+
for (const pairing of pairings) {
305+
const partners = pairing.userIds
306+
.filter((id) => id !== userId)
307+
.map((id) => `<@${id}>`)
308+
.join(", ");
309+
310+
const date = moment(pairing.createdAt)
311+
.tz("America/New_York")
312+
.format("MMM D, YYYY");
313+
314+
let status = "";
315+
if (pairing.isActive) {
316+
status = "🟢 Active";
317+
} else if (pairing.meetupConfirmed) {
318+
status = "✅ Met";
319+
} else {
320+
status = "❌ Did not meet";
321+
}
322+
323+
historyLines.push(`• ${date} - ${partners} ${status}`);
324+
}
325+
326+
await say({
327+
text: `Your coffee chat history`,
328+
blocks: [
329+
{
330+
type: "header",
331+
text: {
332+
type: "plain_text",
333+
text: "☕ Your Coffee Chat History",
334+
emoji: true,
335+
},
336+
},
337+
{
338+
type: "section",
339+
text: {
340+
type: "mrkdwn",
341+
text: `You've been paired *${pairings.length} time${pairings.length !== 1 ? "s" : ""}* in this channel:\n\n${historyLines.join("\n")}`,
342+
},
343+
},
344+
{
345+
type: "context",
346+
elements: [
347+
{
348+
type: "mrkdwn",
349+
text: "🟢 Active pairing • ✅ Met • ❌ Did not meet",
350+
},
351+
],
352+
},
353+
],
354+
});
355+
} catch (error) {
356+
await say(`❌ Error retrieving pairing history: ${error}`);
357+
}
358+
});
208359
}

src/coffeeChats/coffeeChatModels.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class CoffeeChatPairing {
2424

2525
@prop({ default: false })
2626
photosPosted!: boolean;
27+
28+
@prop({ default: false })
29+
meetupConfirmed!: boolean;
2730
}
2831

2932
class CoffeeChatConfig {
@@ -51,6 +54,9 @@ class CoffeeChatUserPreference {
5154
@prop({ default: true })
5255
isOptedIn!: boolean;
5356

57+
@prop({ default: false })
58+
skipNextPairing!: boolean;
59+
5460
@prop()
5561
updatedAt?: Date;
5662
}

0 commit comments

Comments
 (0)