Skip to content

Commit 24ad764

Browse files
Merge pull request #264 from blockful/fix/slack-dao-settings-toggle-ui
fix(consumers): Slack DAO/Settings selection broken (invalid_blocks) → toggle lists
2 parents a65535f + 015eb84 commit 24ad764

4 files changed

Lines changed: 211 additions & 137 deletions

File tree

‎apps/consumers/src/services/bot/slack-bot.service.ts‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,15 @@ export class SlackBotService implements BotServiceInterface {
9696
}
9797
});
9898

99-
handlers.action('dao_checkboxes', async (ctx) => {
100-
await ctx.ack();
99+
handlers.action(/^dao_toggle_/, async (ctx) => {
100+
if (this.daoService) {
101+
const daoId = ctx.body.actions?.[0]?.value;
102+
if (daoId) {
103+
await this.daoService.toggle(ctx, daoId);
104+
} else {
105+
await ctx.ack();
106+
}
107+
}
101108
});
102109

103110

@@ -140,8 +147,15 @@ export class SlackBotService implements BotServiceInterface {
140147
}
141148
});
142149

143-
handlers.action('settings_checkboxes', async (ctx) => {
144-
await ctx.ack();
150+
handlers.action(/^settings_toggle_/, async (ctx) => {
151+
if (this.settingsService) {
152+
const typeId = ctx.body.actions?.[0]?.value;
153+
if (typeId) {
154+
await this.settingsService.toggle(ctx, typeId);
155+
} else {
156+
await ctx.ack();
157+
}
158+
}
145159
});
146160

147161
handlers.action('settings_confirm', async (ctx) => {

‎apps/consumers/src/services/dao/slack-dao.service.ts‎

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import { SlackCommandContext, SlackActionContext } from '../../interfaces/slack-
99

1010
export type SlackDAORequest = Pick<SlackActionContext, 'body' | 'session' | 'ack' | 'respond'>;
1111
import { slackMessages, replacePlaceholders } from '@notification-system/messages';
12-
import type { ViewStateSelectedOption } from '@slack/bolt';
1312
import {
14-
daoSelectionList,
13+
daoToggleList,
1514
errorMessage,
1615
daoEmptyState,
1716
daoListWithEdit
@@ -50,10 +49,10 @@ export class SlackDAOService extends BaseDAOService {
5049
const userPreferences = await this.getUserSubscriptions(fullUserId);
5150
const currentSelections = new Set(userPreferences);
5251

53-
const blocks = daoSelectionList(
52+
const blocks = daoToggleList(
5453
daos,
5554
currentSelections,
56-
'dao_checkboxes',
55+
'dao_toggle',
5756
'dao_confirm_subscribe',
5857
slackMessages.dao.subscribeInstructions
5958
);
@@ -121,64 +120,83 @@ export class SlackDAOService extends BaseDAOService {
121120
}
122121

123122
/**
124-
* Confirm DAO selection changes from checkboxes
123+
* Toggle a single DAO subscription on/off, then re-render the list in place.
124+
* Option 2 UI: each DAO is a button that saves immediately on click — there is
125+
* no batch "confirm" step for the selection itself.
125126
*/
126-
async confirm(context: SlackActionContext): Promise<void> {
127-
const channelId = context.body.channel?.id;
128-
const workspaceId = context.body.team?.id || context.body.user?.team_id;
127+
async toggle(context: SlackActionContext, daoId: string): Promise<void> {
128+
const channelId = context.body.channel?.id || context.body.channel_id;
129+
const workspaceId = context.body.team?.id || context.body.team_id || context.body.user?.team_id;
129130
const fullUserId = `${workspaceId}:${channelId}`;
130131

131132
try {
132133
await context.ack();
133134

134-
// Extract selected DAOs from checkbox state
135-
const state = context.body.state;
136-
if (typeof state === 'string') {
137-
throw new Error('Unexpected DialogAction state format');
135+
const normalized = daoId.toUpperCase();
136+
const daos = await this.fetchAvailableDAOs();
137+
const current = new Set(
138+
await this.subscriptionApi.getUserPreferences(fullUserId, this.getPlatformId(), daos.map(dao => dao.id))
139+
);
140+
141+
// Flip just the clicked DAO and persist that single change
142+
const willSubscribe = !current.has(normalized);
143+
await this.subscriptionApi.saveUserPreference(normalized, fullUserId, this.getPlatformId(), willSubscribe);
144+
if (willSubscribe) {
145+
current.add(normalized);
146+
} else {
147+
current.delete(normalized);
138148
}
139149

140-
const selectedOptions: ViewStateSelectedOption[] =
141-
state?.values?.dao_checkboxes_block?.dao_checkboxes?.selected_options || [];
142-
const selectedDAOs = new Set<string>(selectedOptions.map(opt => opt.value));
150+
if (context.respond) {
151+
await context.respond({
152+
blocks: daoToggleList(daos, current, 'dao_toggle', 'dao_confirm_subscribe', slackMessages.dao.subscribeInstructions),
153+
response_type: 'in_channel',
154+
replace_original: true
155+
});
156+
}
157+
} catch (error) {
158+
this.logger.error({ err: error, event: 'dao.toggle_failed' }, 'error toggling DAO subscription');
159+
if (context.respond) {
160+
await context.respond({
161+
text: slackMessages.dao.updateError,
162+
response_type: 'in_channel'
163+
});
164+
}
165+
}
166+
}
143167

144-
// Sync to the complete desired state (handles both adds and removes)
145-
await this.syncSubscriptionsToState(fullUserId, selectedDAOs);
168+
/**
169+
* "Done" button. Subscriptions were already saved per-click via toggle(), so
170+
* this just collapses the button list into a final summary. Onboarding advance
171+
* (the wallet step) is triggered by the action handler in slack-bot.service.
172+
*/
173+
async confirm(context: SlackActionContext): Promise<void> {
174+
const channelId = context.body.channel?.id || context.body.channel_id;
175+
const workspaceId = context.body.team?.id || context.body.team_id || context.body.user?.team_id;
176+
const fullUserId = `${workspaceId}:${channelId}`;
146177

147-
// Show confirmation message
148-
let successMessage: string;
149-
if (selectedDAOs.size === 0) {
150-
successMessage = slackMessages.dao.unsubscribeAllSuccess;
151-
} else {
152-
const daoList = this.formatDAOList(selectedDAOs);
153-
successMessage = replacePlaceholders(slackMessages.dao.subscribeSuccess, { daoList });
154-
}
178+
try {
179+
await context.ack();
180+
181+
const daos = await this.fetchAvailableDAOs();
182+
const current = await this.subscriptionApi.getUserPreferences(fullUserId, this.getPlatformId(), daos.map(dao => dao.id));
183+
184+
const summaryMessage = current.length === 0
185+
? slackMessages.dao.unsubscribeAllSuccess
186+
: replacePlaceholders(slackMessages.dao.subscribeSuccess, { daoList: this.formatDAOList(current) });
155187

156188
if (context.respond) {
157189
await context.respond({
158-
replace_original: false,
190+
replace_original: true,
159191
blocks: [
160-
{
161-
type: 'section',
162-
text: {
163-
type: 'mrkdwn',
164-
text: successMessage
165-
}
166-
},
167-
{
168-
type: 'context',
169-
elements: [
170-
{
171-
type: 'mrkdwn',
172-
text: slackMessages.dao.updateInstructions
173-
}
174-
]
175-
}
192+
{ type: 'section', text: { type: 'mrkdwn', text: summaryMessage } },
193+
{ type: 'context', elements: [{ type: 'mrkdwn', text: slackMessages.dao.updateInstructions }] }
176194
],
177195
response_type: 'in_channel'
178196
});
179197
}
180198
} catch (error) {
181-
this.logger.error({ err: error, event: 'dao.update_failed' }, 'error updating subscriptions');
199+
this.logger.error({ err: error, event: 'dao.confirm_failed' }, 'error finalizing DAO selection');
182200
if (context.respond) {
183201
await context.respond({
184202
replace_original: false,

0 commit comments

Comments
 (0)