Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions __tests__/components/features/sidebar/sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type NavigationContextValue,
} from "#/context/navigation-context";
import translations from "#/i18n/translation.json";
import { OPENHANDS_SLACK_INVITE_URL } from "#/utils/constants";

// The global `useTranslation` mock in `vitest.setup.ts` returns the key
// as-is. Override it here so `t(...)` resolves keys via the source-of-truth
Expand Down Expand Up @@ -396,6 +397,26 @@ describe("Sidebar", () => {
}
});

it("renders a Join Slack link in the expanded sidebar footer", () => {
renderSidebar("/conversations");

const link = screen.getByTestId("sidebar-join-slack-link");
expect(link).toHaveAttribute("href", OPENHANDS_SLACK_INVITE_URL);
expect(link).toHaveAttribute("target", "_blank");
expect(link).toHaveAttribute("rel", "noopener noreferrer");
expect(link).toHaveTextContent("Join Slack");
});

it("renders a Join Slack icon link when the sidebar is collapsed", () => {
window.localStorage.setItem("openhands-sidebar-collapsed", "true");
renderSidebar("/conversations");

const link = screen.getByTestId("collapsed-join-slack-link");
expect(link).toHaveAttribute("href", OPENHANDS_SLACK_INVITE_URL);
expect(link).toHaveAttribute("target", "_blank");
expect(link).toHaveAttribute("aria-label", "Join Slack");
});

it("renders the renamed top-level nav labels", () => {
// Arrange
renderSidebar("/conversations");
Expand Down
31 changes: 30 additions & 1 deletion src/components/features/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Server,
Settings,
} from "lucide-react";
import { SiSlack } from "react-icons/si";
import { OpenHandsLogoButton } from "#/components/shared/buttons/openhands-logo-button";
import { SidebarNavLink } from "./sidebar-nav-link";
import { getErrorStatus, useSettings } from "#/hooks/query/use-settings";
Expand All @@ -23,6 +24,7 @@ import { SidebarCollapseContext } from "./sidebar-collapse-context";
import { useSidebarCollapsedState } from "#/hooks/use-sidebar-collapsed";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
import { useBackendsHealth } from "#/hooks/query/use-backends-health";
import { OPENHANDS_SLACK_INVITE_URL } from "#/utils/constants";
import AutomationsIcon from "#/icons/automations.svg?react";

// The LLM settings modal is only mounted when the settings query 404s and
Expand Down Expand Up @@ -147,6 +149,7 @@ export function Sidebar() {
currentPath.startsWith("/skills") ||
currentPath === "/plugins" ||
currentPath === "/mcp";
const joinSlackLabel = t(I18nKey.SIDEBAR$JOIN_SLACK);

return (
<SidebarCollapseContext.Provider value={collapsed}>
Expand Down Expand Up @@ -327,6 +330,16 @@ export function Sidebar() {

{collapsed && (
<div className="hidden md:flex md:flex-col md:items-center mt-auto gap-2 pb-2 cursor-pointer">
<a
href={OPENHANDS_SLACK_INVITE_URL}
target="_blank"
rel="noopener noreferrer"
data-testid="collapsed-join-slack-link"
aria-label={joinSlackLabel}
className="inline-flex items-center justify-center w-10 h-10 p-0 mx-auto rounded-md transition-colors text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)]"
>
<SiSlack width={16} height={16} aria-hidden />
</a>
<button
type="button"
data-testid="collapsed-settings-link"
Expand Down Expand Up @@ -420,7 +433,23 @@ export function Sidebar() {
visual separator above it. Hidden in collapsed mode because the
control needs full-width space. */}
{!collapsed && (
<div className="hidden md:flex md:flex-col md:items-stretch pt-2 border-t border-[var(--oh-border)] md:-mx-2 md:px-2">
<div className="hidden md:flex md:flex-col md:items-stretch pt-2 border-t border-[var(--oh-border)] md:-mx-2 md:px-2 gap-1">
<a
href={OPENHANDS_SLACK_INVITE_URL}
target="_blank"
rel="noopener noreferrer"
data-testid="sidebar-join-slack-link"
className={cn(
"flex items-center gap-2 rounded-md transition-colors",
"text-sm leading-5 truncate px-2 py-2 w-full",
"text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)]",
)}
>
<span className="shrink-0 flex items-center justify-center">
<SiSlack width={ICON_SIZE} height={ICON_SIZE} aria-hidden />
</span>
<span className="truncate">{joinSlackLabel}</span>
</a>
<BackendSelector openUpward />
</div>
)}
Expand Down
17 changes: 17 additions & 0 deletions src/i18n/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -14959,6 +14959,23 @@
"uk": "Документація",
"ca": "Documentació"
},
"SIDEBAR$JOIN_SLACK": {
"en": "Join Slack",
"ja": "Slackに参加",
"zh-CN": "加入 Slack",
"zh-TW": "加入 Slack",
"ko-KR": "Slack 참여",
"no": "Bli med i Slack",
"it": "Unisciti a Slack",
"pt": "Entrar no Slack",
"es": "Unirse a Slack",
"ar": "انضم إلى Slack",
"fr": "Rejoindre Slack",
"tr": "Slack'e katıl",
"de": "Slack beitreten",
"uk": "Приєднатися до Slack",
"ca": "Uneix-te a Slack"
},
"SUGGESTIONS$ADD_DOCS": {
"en": "Add best practices docs for contributors",
"ja": "コントリビューター向けのベストプラクティスドキュメントを追加",
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const PRODUCT_URL = {
PRODUCTION: "https://app.all-hands.dev",
};

export const OPENHANDS_SLACK_INVITE_URL = "https://openhands.dev/joinslack";

export const SETTINGS_FORM = {
LABEL_CLASSNAME: "text-[11px] font-medium leading-4 tracking-[0.11px]",
};
Expand Down