// ---------------------------------------------------------------------------// Integration add-flow helpers — the plugin-agnostic pieces every Add-integration// form repeats: decoding a mutation Exit into a user-facing message, the// IntegrationAlreadyExistsError guard, the live slug-collision check against// the catalog, and the inline error/collision alerts.// ---------------------------------------------------------------------------import { useMemo } from "react";import { useAtomValue } from "@effect/atom-react";import { Link } from "@tanstack/react-router";import * as Exit from "effect/Exit";import * as Option from "effect/Option";import * as Predicate from "effect/Predicate";import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";import { integrationsOptimisticAtom } from "../api/atoms";import { messageFromExit } from "../api/error-reporting";/** The failed Exit's `message`, or `fallback` when the error carries none. */export const errorMessageFromExit: (exit: Exit.Exit<unknown, unknown>, fallback: string) => string = messageFromExit;export const isIntegrationAlreadyExistsExit = (exit: Exit.Exit<unknown, unknown>): boolean => Option.match(Exit.findErrorOption(exit), { onNone: () => false, onSome: Predicate.isTagged("IntegrationAlreadyExistsError"), });export const integrationExistsMessage = (slug: string): string => `An integration named "${slug}" already exists. To add more authentication, update your existing integration.`;/** Decode an add-integration failure into its inline message: the * already-exists guard message for a slug collision, otherwise the error's * own message (or `fallback`). */export const addIntegrationErrorMessage = ( exit: Exit.Exit<unknown, unknown>, slug: string, fallback: string,): string => isIntegrationAlreadyExistsExit(exit) ? integrationExistsMessage(slug) : errorMessageFromExit(exit, fallback);/** Whether `slug` is already taken in the tenant-scoped catalog (pre-empting * the API's `IntegrationAlreadyExistsError`). Blank slugs never collide — * they let the server assign one. */export function useSlugAlreadyExists(slug: string): boolean { const integrationsResult = useAtomValue(integrationsOptimisticAtom); return useMemo( () => slug.length > 0 && AsyncResult.isSuccess(integrationsResult) && integrationsResult.value.some((integration) => String(integration.slug) === slug), [integrationsResult, slug], );}/** Inline destructive alert for a form-level error message. */export function FormErrorAlert(props: { readonly message: string }) { return ( <div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2"> <p className="text-[12px] text-destructive">{props.message}</p> </div> );}/** The slug-collision alert with a link to the existing integration. Render * gated by the caller (typically `show && !adding`). */export function SlugCollisionAlert(props: { readonly slug: string }) { return ( <div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2"> <p className="text-[12px] text-destructive"> An integration named "{props.slug}" already exists. To add more authentication, update your existing integration.{" "} <Link to="/{-$orgSlug}/integrations/$namespace" params={{ namespace: props.slug }} className="font-medium underline underline-offset-2" > Open it </Link> </p> </div> );}
// ---------------------------------------------------------------------------
// Integration add-flow helpers — the plugin-agnostic pieces every Add-integration
// form repeats: decoding a mutation Exit into a user-facing message, the
// IntegrationAlreadyExistsError guard, the live slug-collision check against
// the catalog, and the inline error/collision alerts.
// ---------------------------------------------------------------------------
import { useMemo } from "react";
import { useAtomValue } from "@effect/atom-react";
import { Link } from "@tanstack/react-router";
import * as Exit from "effect/Exit";
import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
import { integrationsOptimisticAtom } from "../api/atoms";
import { messageFromExit } from "../api/error-reporting";
/** The failed Exit's `message`, or `fallback` when the error carries none. */
export const errorMessageFromExit: (exit: Exit.Exit<unknown, unknown>, fallback: string) => string =
messageFromExit;
export const isIntegrationAlreadyExistsExit = (exit: Exit.Exit<unknown, unknown>): boolean =>
Option.match(Exit.findErrorOption(exit), {
onNone: () => false,
onSome: Predicate.isTagged("IntegrationAlreadyExistsError"),
});
export const integrationExistsMessage = (slug: string): string =>
`An integration named "${slug}" already exists. To add more authentication, update your existing integration.`;
/** Decode an add-integration failure into its inline message: the
* already-exists guard message for a slug collision, otherwise the error's
* own message (or `fallback`). */
export const addIntegrationErrorMessage = (
exit: Exit.Exit<unknown, unknown>,
slug: string,
fallback: string,
): string =>
isIntegrationAlreadyExistsExit(exit)
? integrationExistsMessage(slug)
: errorMessageFromExit(exit, fallback);
/** Whether `slug` is already taken in the tenant-scoped catalog (pre-empting
* the API's `IntegrationAlreadyExistsError`). Blank slugs never collide —
* they let the server assign one. */
export function useSlugAlreadyExists(slug: string): boolean {
const integrationsResult = useAtomValue(integrationsOptimisticAtom);
return useMemo(
() =>
slug.length > 0 &&
AsyncResult.isSuccess(integrationsResult) &&
integrationsResult.value.some((integration) => String(integration.slug) === slug),
[integrationsResult, slug],
);
}
/** Inline destructive alert for a form-level error message. */
export function FormErrorAlert(props: { readonly message: string }) {
return (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
<p className="text-[12px] text-destructive">{props.message}</p>
</div>
);
}
/** The slug-collision alert with a link to the existing integration. Render
* gated by the caller (typically `show && !adding`). */
export function SlugCollisionAlert(props: { readonly slug: string }) {
return (
<div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
<p className="text-[12px] text-destructive">
An integration named "{props.slug}" already exists. To add more authentication,
update your existing integration.{" "}
<Link
to="/{-$orgSlug}/integrations/$namespace"
params={{ namespace: props.slug }}
className="font-medium underline underline-offset-2"
>
Open it
</Link>
</p>
</div>
);
}