88 type ChipDropdownOption ,
99 ChipInput ,
1010 ChipModalField ,
11- SecretInput ,
1211 Wizard ,
1312} from '@sim/emcn'
1413import { Loader , Plus , Trash } from '@sim/emcn/icons'
@@ -19,6 +18,11 @@ import { SlackIcon } from '@/components/icons'
1918import { SlackAppManifest } from '@/components/integrations/slack-app-manifest'
2019import { resourceScopeFields , resourceScopeFromOwner } from '@/lib/core/resource-scope'
2120import { getBaseUrl } from '@/lib/core/utils/urls'
21+ import {
22+ buildSlackAppCreationUrl ,
23+ getSlackAppNameError ,
24+ SLACK_APP_CREATION_URL_MAX_LENGTH ,
25+ } from '@/lib/integrations/slack-manifest'
2226import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types'
2327import {
2428 useCreateScopedCredential ,
@@ -167,6 +171,7 @@ export function ConnectSlackBotModal({
167171 // window.location.origin) so Slack's servers can reach it.
168172 const requestUrl = buildSlackCustomBotRequestUrl ( credentialId )
169173
174+ const nameError = isReconnect ? null : getSlackAppNameError ( appName )
170175 const descriptionError = getAgentDescriptionError ( appDescription )
171176 const slashCommandsError = searchOnly || isReconnect ? null : getSlashCommandsError ( slashCommands )
172177 const manifestConfigurationError = descriptionError ?? slashCommandsError
@@ -192,7 +197,7 @@ export function ConnectSlackBotModal({
192197 ) ,
193198 ...( managedUserAuthorization ? { managedUserAuthorization } : { } ) ,
194199 } )
195- return JSON . stringify ( manifest , null , 2 )
200+ return JSON . stringify ( manifest )
196201 } , [
197202 isReconnect ,
198203 manifestConfigurationError ,
@@ -204,6 +209,12 @@ export function ConnectSlackBotModal({
204209 searchOnly ,
205210 ] )
206211
212+ const createAppUrl = buildSlackAppCreationUrl ( manifestJson )
213+ const creationUrlError =
214+ createAppUrl . length > SLACK_APP_CREATION_URL_MAX_LENGTH
215+ ? 'This app configuration is too large to open in Slack. Shorten or remove slash commands.'
216+ : null
217+
207218 const capabilityIds = [ ...selected ]
208219 const setCapabilityIds = ( next : string [ ] ) => setSelected ( new Set ( next ) )
209220
@@ -270,32 +281,42 @@ export function ConnectSlackBotModal({
270281 fallback, which collides for a second bot in the same workspace. */ }
271282 < Wizard . Step
272283 title = { searchOnly ? 'Name your Slack app' : 'Configure your bot' }
273- canAdvance = { appName . trim ( ) . length > 0 && ! descriptionError && ! slashCommandsError }
284+ canAdvance = {
285+ appName . trim ( ) . length > 0 &&
286+ ! nameError &&
287+ ! manifestConfigurationError &&
288+ ! creationUrlError
289+ }
274290 >
275291 < StepConfigure
276292 searchOnly = { searchOnly }
277293 reconnect = { isReconnect }
278294 appName = { appName }
295+ nameError = { appName ? nameError : null }
279296 onAppNameChange = { setAppName }
280297 appDescription = { appDescription }
281298 onAppDescriptionChange = { setAppDescription }
282299 descriptionError = { descriptionError }
283300 slashCommands = { slashCommands }
284301 onSlashCommandsChange = { setSlashCommands }
285- slashCommandsError = { slashCommandsError }
302+ slashCommandsError = { slashCommandsError ?? creationUrlError }
286303 capabilityIds = { capabilityIds }
287304 onCapabilityIdsChange = { setCapabilityIds }
288305 />
289306 </ Wizard . Step >
290307 < Wizard . Step title = { isReconnect ? 'Open your app in Slack' : 'Create the app in Slack' } >
291- < StepCreate manifestJson = { manifestJson } reconnect = { isReconnect } />
308+ < StepCreate
309+ manifestJson = { manifestJson }
310+ createAppUrl = { createAppUrl }
311+ reconnect = { isReconnect }
312+ />
313+ </ Wizard . Step >
314+ < Wizard . Step title = 'Install and paste your Bot Token' canAdvance = { botToken . trim ( ) . length > 0 } >
315+ < StepToken value = { botToken } onChange = { setBotToken } reconnect = { isReconnect } />
292316 </ Wizard . Step >
293317 < Wizard . Step title = 'Paste your Signing Secret' canAdvance = { signingSecret . trim ( ) . length > 0 } >
294318 < StepSecret value = { signingSecret } onChange = { setSigningSecret } />
295319 </ Wizard . Step >
296- < Wizard . Step title = 'Install and paste your Bot Token' canAdvance = { botToken . trim ( ) . length > 0 } >
297- < StepToken value = { botToken } onChange = { setBotToken } />
298- </ Wizard . Step >
299320 < Wizard . Step title = 'All set' >
300321 < StepDone
301322 searchOnly = { searchOnly }
@@ -337,6 +358,7 @@ interface StepConfigureProps {
337358 searchOnly : boolean
338359 reconnect : boolean
339360 appName : string
361+ nameError : string | null
340362 onAppNameChange : ( next : string ) => void
341363 appDescription : string
342364 onAppDescriptionChange : ( next : string ) => void
@@ -351,6 +373,7 @@ function StepConfigure({
351373 searchOnly,
352374 reconnect,
353375 appName,
376+ nameError,
354377 onAppNameChange,
355378 appDescription,
356379 onAppDescriptionChange,
@@ -372,6 +395,7 @@ function StepConfigure({
372395 value = { appName }
373396 onChange = { onAppNameChange }
374397 placeholder = { DEFAULT_APP_NAME }
398+ error = { nameError }
375399 />
376400 < ChipModalField
377401 type = 'input'
@@ -491,9 +515,10 @@ function SlashCommandsEditor({ commands, onChange, error }: SlashCommandsEditorP
491515
492516interface StepCreateProps {
493517 manifestJson : string
518+ createAppUrl : string
494519 reconnect : boolean
495520}
496- function StepCreate ( { manifestJson, reconnect } : StepCreateProps ) {
521+ function StepCreate ( { manifestJson, createAppUrl , reconnect } : StepCreateProps ) {
497522 if ( reconnect ) {
498523 return (
499524 < SubStepList >
@@ -521,29 +546,13 @@ function StepCreate({ manifestJson, reconnect }: StepCreateProps) {
521546 < div className = 'space-y-4' >
522547 < SubStepList >
523548 < SubStep n = { 1 } >
524- < div > Copy your manifest:</ div >
549+ < div > Open Slack with the manifest for your selected permissions already filled in :</ div >
525550 < div className = 'mt-2' >
526- < SlackAppManifest manifest = { manifestJson } />
551+ < SlackAppManifest manifest = { manifestJson } createAppUrl = { createAppUrl } />
527552 </ div >
528553 </ SubStep >
529554 < SubStep n = { 2 } >
530- Open the{ ' ' }
531- < a
532- href = 'https://api.slack.com/apps'
533- target = '_blank'
534- rel = 'noopener noreferrer'
535- className = 'text-[var(--brand-secondary)] underline underline-offset-2'
536- >
537- Slack Apps page
538- </ a >
539- .
540- </ SubStep >
541- < SubStep n = { 3 } >
542- Click < strong > Create New App</ strong > → < strong > From a manifest</ strong > and pick your
543- workspace.
544- </ SubStep >
545- < SubStep n = { 4 } >
546- Paste your manifest, then click < strong > Next</ strong > → < strong > Create</ strong > .
555+ Select your workspace, review the configuration, then click < strong > Create</ strong > .
547556 </ SubStep >
548557 </ SubStepList >
549558 </ div >
@@ -576,13 +585,22 @@ function StepSecret({ value, onChange }: SecretStepProps) {
576585 )
577586}
578587
579- function StepToken ( { value, onChange } : SecretStepProps ) {
588+ function StepToken ( { value, onChange, reconnect } : SecretStepProps & { reconnect : boolean } ) {
580589 return (
581590 < div className = 'space-y-4' >
582591 < SubStepList >
583592 < SubStep n = { 1 } >
584- In Slack, open < strong > Install App</ strong > → < strong > Install to Workspace</ strong > and
585- authorize.
593+ { reconnect ? (
594+ < >
595+ Open < strong > OAuth & Permissions</ strong > in your existing Slack app. Reinstall
596+ only if Slack requests it.
597+ </ >
598+ ) : (
599+ < >
600+ In Slack, open < strong > OAuth & Permissions</ strong > →{ ' ' }
601+ < strong > Install to Workspace</ strong > and approve access.
602+ </ >
603+ ) }
586604 </ SubStep >
587605 < SubStep n = { 2 } >
588606 Copy the < strong > Bot User OAuth Token</ strong > (starts with < code > xoxb-</ code > ).
@@ -602,9 +620,15 @@ interface SecretFieldProps {
602620}
603621function SecretField ( { label, value, onChange, placeholder } : SecretFieldProps ) {
604622 return (
605- < ChipModalField type = 'custom' title = { label } >
606- < SecretInput value = { value } onChange = { onChange } placeholder = { placeholder } />
607- </ ChipModalField >
623+ < ChipModalField
624+ type = 'input'
625+ inputType = 'password'
626+ title = { label }
627+ value = { value }
628+ onChange = { onChange }
629+ placeholder = { placeholder }
630+ autoComplete = 'off'
631+ />
608632 )
609633}
610634
0 commit comments