@@ -36,7 +36,9 @@ import {
3636 Crown ,
3737 TriangleAlert ,
3838 Info ,
39+ Loader2 ,
3940} from "lucide-react" ;
41+ import { Input } from "@/components/ui/input" ;
4042import { Switch } from "@/components/ui/switch" ;
4143import {
4244 Tooltip ,
@@ -101,6 +103,8 @@ export default function SettingsGeneral({
101103 const [ addChannelOpen , setAddChannelOpen ] = useState ( false ) ;
102104 const [ selectedSlackChannel , setSelectedSlackChannel ] = useState ( "" ) ;
103105 const [ addingChannel , setAddingChannel ] = useState ( false ) ;
106+ const [ manualMode , setManualMode ] = useState ( false ) ;
107+ const [ manualChannelName , setManualChannelName ] = useState ( "" ) ;
104108 const [ deleteTarget , setDeleteTarget ] = useState < Channel | null > ( null ) ;
105109
106110 useEffect ( ( ) => {
@@ -154,6 +158,60 @@ export default function SettingsGeneral({
154158 }
155159 }
156160
161+ async function addChannelManually ( ) {
162+ const name = manualChannelName . trim ( ) . replace ( / ^ # / , "" ) ;
163+ if ( ! name ) return ;
164+
165+ setAddingChannel ( true ) ;
166+ try {
167+ const lookupRes = await fetch ( "/api/settings/slack-channels" , {
168+ method : "POST" ,
169+ headers : {
170+ "Content-Type" : "application/json" ,
171+ "X-Workspace-Id" : workspaceId ,
172+ } ,
173+ body : JSON . stringify ( { channelName : name } ) ,
174+ } ) ;
175+
176+ if ( ! lookupRes . ok ) {
177+ const data = await lookupRes . json ( ) ;
178+ toast . error ( data . error || "Channel not found" ) ;
179+ return ;
180+ }
181+
182+ const slackChannel = await lookupRes . json ( ) ;
183+
184+ const res = await fetch ( "/api/settings/channels" , {
185+ method : "POST" ,
186+ headers : {
187+ "Content-Type" : "application/json" ,
188+ "X-Workspace-Id" : workspaceId ,
189+ } ,
190+ body : JSON . stringify ( {
191+ slackChannelId : slackChannel . id ,
192+ channelName : slackChannel . name ,
193+ } ) ,
194+ } ) ;
195+
196+ if ( ! res . ok ) {
197+ const data = await res . json ( ) ;
198+ toast . error ( data . error || "Failed to add channel" ) ;
199+ return ;
200+ }
201+
202+ const channel = await res . json ( ) ;
203+ setChannels ( ( prev ) => [ ...prev , { ...channel , disabledStyleIds : [ ] } ] ) ;
204+ setAddChannelOpen ( false ) ;
205+ setManualChannelName ( "" ) ;
206+ setManualMode ( false ) ;
207+ toast . success ( `#${ slackChannel . name } connected` ) ;
208+ } catch {
209+ toast . error ( "Failed to add channel" ) ;
210+ } finally {
211+ setAddingChannel ( false ) ;
212+ }
213+ }
214+
157215 async function removeChannel ( channel : Channel ) {
158216 setChannels ( ( prev ) => prev . filter ( ( ch ) => ch . id !== channel . id ) ) ;
159217 setDeleteTarget ( null ) ;
@@ -427,20 +485,39 @@ export default function SettingsGeneral({
427485 quotes.
428486 </ DialogDescription >
429487 </ DialogHeader >
430- { availableSlackChannels . length === 0 ? (
431- < div className = "flex items-start gap-3 rounded-md border border-yellow-500/30 bg-yellow-500/10 p-3 text-sm" >
432- < TriangleAlert className = "mt-0.5 h-5 w-5 shrink-0 text-yellow-500" />
433- < div >
434- < p className = "font-medium text-yellow-500" >
435- No channels available
436- </ p >
437- < p className = "text-muted-foreground mt-1" >
438- The No Context bot needs to be added to a Slack
439- channel first. In Slack, open the channel you want to
440- monitor, click the channel name at the top, go to the{ " " }
441- < strong > Integrations</ strong > tab, and add the{ " " }
442- < strong > No Context</ strong > app.
443- </ p >
488+ { manualMode || availableSlackChannels . length === 0 ? (
489+ < div className = "space-y-3" >
490+ { availableSlackChannels . length === 0 && ! manualMode && (
491+ < div className = "flex items-start gap-3 rounded-md border border-yellow-500/30 bg-yellow-500/10 p-3 text-sm" >
492+ < TriangleAlert className = "mt-0.5 h-5 w-5 shrink-0 text-yellow-500" />
493+ < div >
494+ < p className = "font-medium text-yellow-500" >
495+ No channels found automatically
496+ </ p >
497+ < p className = "text-muted-foreground mt-1" >
498+ You can manually enter a channel name below, or
499+ add the No Context bot to a channel in Slack
500+ first.
501+ </ p >
502+ </ div >
503+ </ div >
504+ ) }
505+ < div className = "relative" >
506+ < span className = "text-muted-foreground pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm font-medium" >
507+ #
508+ </ span >
509+ < Input
510+ type = "text"
511+ value = { manualChannelName }
512+ onChange = { ( e ) => setManualChannelName ( e . target . value ) }
513+ onKeyDown = { ( e ) => {
514+ if ( e . key === "Enter" && manualChannelName . trim ( ) . replace ( / ^ # / , "" ) ) {
515+ addChannelManually ( ) ;
516+ }
517+ } }
518+ placeholder = "channel-name"
519+ className = "pl-7"
520+ />
444521 </ div >
445522 </ div >
446523 ) : (
@@ -460,19 +537,53 @@ export default function SettingsGeneral({
460537 </ SelectContent >
461538 </ Select >
462539 ) }
540+ { availableSlackChannels . length > 0 && (
541+ < button
542+ type = "button"
543+ onClick = { ( ) => {
544+ setManualMode ( ( m ) => ! m ) ;
545+ setSelectedSlackChannel ( "" ) ;
546+ setManualChannelName ( "" ) ;
547+ } }
548+ className = "text-muted-foreground hover:text-foreground text-xs underline-offset-2 hover:underline"
549+ >
550+ { manualMode
551+ ? "Select from channel list"
552+ : "Manually enter a channel name" }
553+ </ button >
554+ ) }
463555 < DialogFooter >
464556 < Button
465557 variant = "secondary"
466- onClick = { ( ) => setAddChannelOpen ( false ) }
558+ onClick = { ( ) => {
559+ setAddChannelOpen ( false ) ;
560+ setManualMode ( false ) ;
561+ setManualChannelName ( "" ) ;
562+ } }
467563 >
468564 Cancel
469565 </ Button >
470- < Button
471- onClick = { addChannel }
472- disabled = { ! selectedSlackChannel || addingChannel }
473- >
474- { addingChannel ? "Adding..." : "Add Channel" }
475- </ Button >
566+ { manualMode || availableSlackChannels . length === 0 ? (
567+ < Button
568+ onClick = { addChannelManually }
569+ disabled = {
570+ ! manualChannelName . trim ( ) . replace ( / ^ # / , "" ) ||
571+ addingChannel
572+ }
573+ >
574+ { addingChannel && (
575+ < Loader2 className = "mr-1 h-3.5 w-3.5 animate-spin" />
576+ ) }
577+ { addingChannel ? "Adding..." : "Add Channel" }
578+ </ Button >
579+ ) : (
580+ < Button
581+ onClick = { addChannel }
582+ disabled = { ! selectedSlackChannel || addingChannel }
583+ >
584+ { addingChannel ? "Adding..." : "Add Channel" }
585+ </ Button >
586+ ) }
476587 </ DialogFooter >
477588 </ DialogContent >
478589 </ Dialog >
0 commit comments