Add Zendesk connector logo and connect dialog

Add the Zendesk brand mark under ThirdParties and wire it into the
logo map. Because the customer subdomain is free-form (not a fixed
region list), the connect flow opens a dialog with a subdomain text
input that is sanitized and passed as ?site= to the OAuth initiate
endpoint, mirroring the Datadog multi-site dialog.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-06-04 18:33:05 +02:00
parent dbd920dc24
commit 09cfbce6e5
4 changed files with 83 additions and 4 deletions

View File

@@ -171,6 +171,17 @@ function hasRequiredExtraSettings(
.every(s => values[s.key]?.trim());
}
// Accepts either a bare subdomain ("acme") or a pasted host
// ("https://acme.zendesk.com/") and reduces it to the bare subdomain the
// backend expects as the `site` query param.
function cleanZendeskSubdomain(raw: string): string {
let value = raw.trim();
value = value.replace(/^https?:\/\//i, "");
value = value.replace(/\/+$/, "");
value = value.replace(/\.zendesk\.com$/i, "");
return value.trim();
}
export function AddAccessSourceDialog({
children,
organizationId,
@@ -184,6 +195,7 @@ export function AddAccessSourceDialog({
const apiKeyDialogRef = useDialogRef();
const clientCredentialsDialogRef = useDialogRef();
const datadogDialogRef = useDialogRef();
const zendeskDialogRef = useDialogRef();
const [searchQuery, setSearchQuery] = useState("");
const [activeProvider, setActiveProvider] = useState<ProviderInfo | null>(null);
@@ -191,6 +203,9 @@ export function AddAccessSourceDialog({
const [datadogSite, setDatadogSite] = useState<string>("US1");
const [datadogProvider, setDatadogProvider] = useState<ProviderInfo | null>(null);
const [zendeskSubdomain, setZendeskSubdomain] = useState<string>("");
const [zendeskProvider, setZendeskProvider] = useState<ProviderInfo | null>(null);
const [apiKeyValue, setApiKeyValue] = useState("");
const [extraSettingValues, setExtraSettingValues] = useState<Record<string, string>>({});
const [isConnectingAPIKey, setIsConnectingAPIKey] = useState(false);
@@ -277,6 +292,12 @@ export function AddAccessSourceDialog({
datadogDialogRef.current?.open();
};
const openZendeskDialog = (info: ProviderInfo) => {
setZendeskProvider(info);
setZendeskSubdomain("");
zendeskDialogRef.current?.open();
};
const createSourceAfterConnector = (
connectorId: string,
displayName: string,
@@ -470,10 +491,15 @@ export function AddAccessSourceDialog({
return (
<Button
variant="secondary"
onClick={() =>
info.provider === "DATADOG"
? openDatadogDialog(info)
: connectOAuthProvider(info)}
onClick={() => {
if (info.provider === "DATADOG") {
openDatadogDialog(info);
} else if (info.provider === "ZENDESK") {
openZendeskDialog(info);
} else {
connectOAuthProvider(info);
}
}}
>
{__("Connect")}
</Button>
@@ -808,6 +834,44 @@ export function AddAccessSourceDialog({
</DialogFooter>
</form>
</Dialog>
<Dialog ref={zendeskDialogRef} title={__("Connect Zendesk")}>
<form
onSubmit={(e) => {
e.preventDefault();
if (zendeskProvider) {
const site = cleanZendeskSubdomain(zendeskSubdomain);
if (site) {
connectOAuthProvider(zendeskProvider, { site });
}
}
}}
>
<DialogContent padded className="space-y-4">
<p className="text-txt-secondary text-sm">
{__("Enter your Zendesk subdomain, then continue to authorize access.")}
</p>
<Field
label={__("Zendesk subdomain")}
placeholder={__("acme")}
value={zendeskSubdomain}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setZendeskSubdomain(e.target.value)}
help={__("The <subdomain> part of <subdomain>.zendesk.com")}
required
autoFocus
/>
</DialogContent>
<DialogFooter>
<Button
type="submit"
disabled={!cleanZendeskSubdomain(zendeskSubdomain)}
>
{__("Continue")}
</Button>
</DialogFooter>
</form>
</Dialog>
</>
);
}

View File

@@ -51,6 +51,7 @@ import { Supabase } from "./Supabase";
import { Tailscale } from "./Tailscale";
import { Tally } from "./Tally";
import { Vercel } from "./Vercel";
import { Zendesk } from "./Zendesk";
const thirdParties: Record<string, FC<ComponentProps<"svg">>> = {
ANTHROPIC: Anthropic,
@@ -93,6 +94,7 @@ const thirdParties: Record<string, FC<ComponentProps<"svg">>> = {
TAILSCALE: Tailscale,
TALLY: Tally,
VERCEL: Vercel,
ZENDESK: Zendesk,
};
type ThirdPartyLogoProps = ComponentProps<"svg"> & {

View File

@@ -0,0 +1,12 @@
import type { ComponentProps } from "react";
export function Zendesk(props: ComponentProps<"svg">) {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M12.914 2.904V16.29L24 2.905H12.914zM0 2.906C0 5.966 2.483 8.45 5.543 8.45s5.542-2.484 5.543-5.544H0zm11.086 4.807L0 21.096h11.086V7.713zm7.37 7.84c-3.063 0-5.542 2.48-5.542 5.543H24c0-3.06-2.48-5.543-5.543-5.543z"
fill="#03363D"
/>
</svg>
);
}

View File

@@ -36,3 +36,4 @@ export { Tally } from "./Tally";
export { Tailscale } from "./Tailscale";
export { ThirdPartyLogo } from "./ThirdPartyLogo";
export { Vercel } from "./Vercel";
export { Zendesk } from "./Zendesk";