Skip to content

Commit 08b412f

Browse files
committed
Fix Slack App Home modal navigation
1 parent d52b8ba commit 08b412f

2 files changed

Lines changed: 118 additions & 10 deletions

File tree

app/api/slack/interactions/route.js

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ import {
2424
slackDumpSavedModalView,
2525
slackFieldNoteEditModalView,
2626
slackFieldNoteDraftingModalView,
27-
slackFieldNoteDraftReadyModalView,
2827
slackFieldNoteDraftErrorModalView,
2928
slackFieldNotePublishedModalView,
3029
slackFieldNoteSavedModalView,
3130
slackFieldNoteReviewPickerView,
3231
slackSavedDumpPayload,
3332
slackLoadingModalView,
3433
slackPinnedSpacesView,
34+
slackPublishedReadsView,
3535
slackPublishOptionsView,
3636
slackPublishPreviewView,
3737
slackRoomModalView,
@@ -57,7 +57,7 @@ export async function POST(request) {
5757
try {
5858
if (actionId === "start_room_modal") {
5959
const teamId = slackTeamId(payload);
60-
const viewId = cleanString(payload.view?.id, 120);
60+
const viewId = slackModalViewId(payload);
6161
if (viewId) {
6262
await updateSlackView({
6363
teamId,
@@ -73,7 +73,7 @@ export async function POST(request) {
7373
}
7474
if (actionId === "new_private_dump") {
7575
const teamId = slackTeamId(payload);
76-
const viewId = cleanString(payload.view?.id, 120);
76+
const viewId = slackModalViewId(payload);
7777
if (viewId) {
7878
await updateSlackView({ teamId, viewId, view: slackDumpModalView() });
7979
} else {
@@ -108,7 +108,7 @@ export async function POST(request) {
108108
if (actionId === "review_field_note_drafts") {
109109
const teamId = slackTeamId(payload);
110110
const slackUserId = cleanString(payload.user?.id, 80);
111-
const viewId = cleanString(payload.view?.id, 120);
111+
const viewId = slackModalViewId(payload);
112112
if (viewId) {
113113
await updateSlackView({
114114
teamId,
@@ -146,10 +146,51 @@ export async function POST(request) {
146146
});
147147
}
148148
}
149+
if (actionId === "review_published_reads") {
150+
const teamId = slackTeamId(payload);
151+
const slackUserId = cleanString(payload.user?.id, 80);
152+
const viewId = slackModalViewId(payload);
153+
if (viewId) {
154+
await updateSlackView({
155+
teamId,
156+
viewId,
157+
view: slackLoadingModalView({ title: "published reads", message: "loading your published reads..." }),
158+
});
159+
after(async () => {
160+
try {
161+
await updateSlackView({
162+
teamId,
163+
viewId,
164+
view: await slackPublishedReadsView({ teamId, slackUserId }),
165+
});
166+
} catch (error) {
167+
console.error("Slack published reads update failed", { message: error.message, slackError: error.slack?.error });
168+
}
169+
});
170+
} else {
171+
const result = await openSlackLoadingModal({
172+
teamId,
173+
triggerId: cleanString(payload.trigger_id, 200),
174+
title: "published reads",
175+
message: "loading your published reads...",
176+
});
177+
after(async () => {
178+
try {
179+
await updateSlackView({
180+
teamId,
181+
viewId: cleanString(result.view?.id, 120),
182+
view: await slackPublishedReadsView({ teamId, slackUserId }),
183+
});
184+
} catch (error) {
185+
console.error("Slack published reads update failed", { message: error.message, slackError: error.slack?.error });
186+
}
187+
});
188+
}
189+
}
149190
if (actionId === "manage_pinned_spaces") {
150191
const teamId = slackTeamId(payload);
151192
const slackUserId = cleanString(payload.user?.id, 80);
152-
const viewId = cleanString(payload.view?.id, 120);
193+
const viewId = slackModalViewId(payload);
153194
if (viewId) {
154195
await updateSlackView({
155196
teamId,
@@ -190,7 +231,7 @@ export async function POST(request) {
190231
if (actionId === "unpin_pinned_space_start") {
191232
const teamId = slackTeamId(payload);
192233
const slackUserId = cleanString(payload.user?.id, 80);
193-
const viewId = cleanString(payload.view?.id, 120);
234+
const viewId = slackModalViewId(payload);
194235
if (viewId) {
195236
await updateSlackView({
196237
teamId,
@@ -207,7 +248,7 @@ export async function POST(request) {
207248
const teamId = slackTeamId(payload);
208249
const slackUserId = cleanString(payload.user?.id, 80);
209250
const fieldNoteId = cleanString(payload.actions?.[0]?.value, 64);
210-
const viewId = cleanString(payload.view?.id, 120);
251+
const viewId = slackModalViewId(payload);
211252
if (viewId) {
212253
await updateSlackView({
213254
teamId,
@@ -298,7 +339,7 @@ export async function POST(request) {
298339
await updateSlackView({
299340
teamId,
300341
viewId: cleanString(payload.view?.id, 120),
301-
view: slackFieldNoteDraftReadyModalView(result),
342+
view: slackFieldNoteEditModalView(result.fieldNote),
302343
});
303344
} catch (error) {
304345
await updateSlackView({
@@ -499,6 +540,10 @@ function slackTeamId(payload) {
499540
return cleanString(payload.team?.id || payload.user?.team_id, 80);
500541
}
501542

543+
function slackModalViewId(payload) {
544+
return payload.view?.type === "modal" ? cleanString(payload.view?.id, 120) : "";
545+
}
546+
502547
function selectedSlackOptionValues(options) {
503548
return Array.isArray(options) ? options.map((option) => cleanString(option?.value, 64)).filter(Boolean) : [];
504549
}

src/server/slack.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,16 @@ export async function slackFieldNoteReviewPickerView({ teamId, slackUserId }) {
508508
}
509509
}
510510

511+
export async function slackPublishedReadsView({ teamId, slackUserId }) {
512+
try {
513+
const connection = await findOrCreateSlackConnectionByEmail({ teamId, slackUserId });
514+
const fieldNotes = await recentSlackPublishedFieldNotes(connection);
515+
return fieldNotes.length ? slackPublishedReadsModal({ fieldNotes }) : slackNoPublishedReadsModal();
516+
} catch (error) {
517+
return slackFieldNoteDraftUnavailableModal(error.message || "connect mumbl first.");
518+
}
519+
}
520+
511521
export async function slackPinnedSpacesView({ teamId, slackUserId }) {
512522
const connection = await findSlackConnection({ teamId, slackUserId });
513523
if (!connection) return slackPinnedSpacesEmptyModal({ connected: false });
@@ -722,6 +732,19 @@ export async function recentSlackFieldNoteDrafts(connection) {
722732
return data || [];
723733
}
724734

735+
export async function recentSlackPublishedFieldNotes(connection) {
736+
const supabase = getSupabaseAdmin();
737+
const { data, error } = await supabase
738+
.from("field_notes")
739+
.select("id, title, content, published_at, spaces:team_room_id(id,slug,name)")
740+
.eq("user_id", connection.mumbl_user_id)
741+
.eq("is_published", true)
742+
.order("published_at", { ascending: false })
743+
.limit(10);
744+
if (error) throw error;
745+
return data || [];
746+
}
747+
725748
export async function getSlackFieldNoteDraft({ teamId, slackUserId, fieldNoteId }) {
726749
const connection = await findOrCreateSlackConnectionByEmail({ teamId, slackUserId });
727750
const supabase = getSupabaseAdmin();
@@ -750,7 +773,7 @@ export async function updateSlackFieldNoteDraft({ teamId, slackUserId, fieldNote
750773
.eq("user_id", connection.mumbl_user_id)
751774
.eq("is_published", false)
752775
.eq("id", cleanString(fieldNoteId, 64))
753-
.select("id, title")
776+
.select("id, title, content")
754777
.single();
755778
if (error) throw error;
756779
return {
@@ -1018,7 +1041,7 @@ export async function createSlackFieldNoteDraft({ teamId, slackUserId, dumpIds }
10181041
title: draft.title || "field note",
10191042
content: draft.content,
10201043
})
1021-
.select("id, title")
1044+
.select("id, title, content")
10221045
.single();
10231046
if (noteError) throw noteError;
10241047

@@ -1421,6 +1444,7 @@ async function slackAppHomeBlocks({ teamId, slackUserId }) {
14211444
actions([
14221445
{ text: "draft team read", actionId: "draft_team_read" },
14231446
{ text: "review drafts", actionId: "review_field_note_drafts" },
1447+
{ text: "published reads", actionId: "review_published_reads" },
14241448
]),
14251449
divider(),
14261450
section(
@@ -1569,6 +1593,45 @@ function slackNoFieldNoteDraftsModal() {
15691593
};
15701594
}
15711595

1596+
function slackPublishedReadsModal({ fieldNotes }) {
1597+
const { appUrl } = getServerEnv();
1598+
const blocks = [
1599+
section("*published reads*\nField notes you have already published from Mumbl or Slack."),
1600+
];
1601+
1602+
fieldNotes.slice(0, 10).forEach((fieldNote) => {
1603+
const space = fieldNote.spaces || {};
1604+
const destination = space.name ? `in ${escapeSlackText(space.name)}` : "published";
1605+
blocks.push(
1606+
section(`*${escapeSlackText(fieldNote.title || "team read")}*\n${destination}`),
1607+
actions([{ text: "open read", url: space.slug ? `${appUrl}/r/${space.slug}/reads` : `${appUrl}/dump?fieldNote=${encodeURIComponent(fieldNote.id)}` }]),
1608+
);
1609+
});
1610+
1611+
return {
1612+
type: "modal",
1613+
title: { type: "plain_text", text: "published reads" },
1614+
close: { type: "plain_text", text: "done" },
1615+
blocks,
1616+
};
1617+
}
1618+
1619+
function slackNoPublishedReadsModal() {
1620+
const { appUrl } = getServerEnv();
1621+
return {
1622+
type: "modal",
1623+
title: { type: "plain_text", text: "published reads" },
1624+
close: { type: "plain_text", text: "done" },
1625+
blocks: [
1626+
section("*nothing published yet.*\nReview a draft when one feels ready for a team read."),
1627+
actions([
1628+
{ text: "review drafts", actionId: "review_field_note_drafts", style: "primary" },
1629+
{ text: "open your dump", url: `${appUrl}/dump` },
1630+
]),
1631+
],
1632+
};
1633+
}
1634+
15721635
function slackNoPinnedSpacesModal() {
15731636
const { appUrl } = getServerEnv();
15741637
return {

0 commit comments

Comments
 (0)