Skip to content

Commit 3ea1e57

Browse files
committed
Add manual channel input to add channel dialog and center billing badge text
Allow users to manually enter a channel name when no channels are found automatically (matching onboarding behavior). Also adds a toggle to switch between dropdown and manual input when channels are available. Centers the "Most popular" badge text on the billing tab. https://claude.ai/code/session_018CBuL3WRnbJPedhSF1LPs7
1 parent 30ba2c4 commit 3ea1e57

File tree

3 files changed

+134
-23
lines changed

3 files changed

+134
-23
lines changed

src/components/dashboard/settings-billing.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ export function SettingsBilling({
518518
}`}
519519
>
520520
{isPopular && !isCurrent && (
521-
<Badge className="absolute -top-2.5 left-1/2 -translate-x-1/2 text-[10px]">
521+
<Badge className="absolute -top-2.5 left-1/2 -translate-x-1/2 text-center text-[10px] whitespace-nowrap">
522522
Most popular
523523
</Badge>
524524
)}

src/components/dashboard/settings-general.tsx

Lines changed: 132 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import {
3636
Crown,
3737
TriangleAlert,
3838
Info,
39+
Loader2,
3940
} from "lucide-react";
41+
import { Input } from "@/components/ui/input";
4042
import { Switch } from "@/components/ui/switch";
4143
import {
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>

src/components/ui/badge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { cva, type VariantProps } from "class-variance-authority";
22
import { cn } from "@/lib/utils";
33

44
const badgeVariants = cva(
5-
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors",
5+
"inline-flex items-center justify-center rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors",
66
{
77
variants: {
88
variant: {

0 commit comments

Comments
 (0)