178 lines · 6.2 KB · TypeScript
RawDownload
import { useCallback, useMemo, useState } from "react";import { useAtomSet } from "@effect/atom-react";import * as Exit from "effect/Exit"; import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";import {  slugifyNamespace,  useIntegrationIdentity,} from "@executor-js/react/plugins/integration-identity";import { Button } from "@executor-js/react/components/button";import { FloatActions } from "@executor-js/react/components/float-actions";import {  addIntegrationErrorMessage,  FormErrorAlert,  SlugCollisionAlert,  useSlugAlreadyExists,} from "@executor-js/react/lib/integration-add";import { OpenApiSourceDetailsFields } from "@executor-js/plugin-openapi/react"; import { addGoogleBundle } from "./atoms";import { GoogleProductPicker } from "./GoogleProductPicker";import {  GOOGLE_PHOTOS_PRESET_ID,  googleOpenApiPresets,  googlePhotosPresetIds,  type GoogleOpenApiPreset,} from "../sdk/presets"; const GOOGLE_BUNDLE_FAVICON = "https://fonts.gstatic.com/s/i/productlogos/googleg/v6/192px.svg"; const googleBundleDefaultPresetIds: ReadonlySet<string> = new Set(  googleOpenApiPresets    .filter((preset: GoogleOpenApiPreset) => preset.featured)    .map((preset: GoogleOpenApiPreset) => preset.id),); const googleBundleUrls = (  selectedPresetIds: ReadonlySet<string>,  customUrls: readonly string[],): readonly string[] => {  const fromPresets = googleOpenApiPresets.flatMap((preset: GoogleOpenApiPreset) =>    preset.url && selectedPresetIds.has(preset.id) ? [preset.url] : [],  );  return [...new Set([...fromPresets, ...customUrls])];}; export default function AddGoogleSource(props: {  onComplete: (slug?: string) => void;  onCancel: () => void;  initialPreset?: string;  initialNamespace?: string;}) {  const isGooglePhotosPreset = props.initialPreset === GOOGLE_PHOTOS_PRESET_ID;  const [selectedPresetIds, setSelectedPresetIds] = useState<ReadonlySet<string>>(    isGooglePhotosPreset ? new Set(googlePhotosPresetIds) : googleBundleDefaultPresetIds,  );  const [customDiscoveryUrls, setCustomDiscoveryUrls] = useState<readonly string[]>([]);  const [baseUrl, setBaseUrl] = useState("");  const [descriptionDraft, setDescriptionDraft] = useState<string | null>(null);  const [adding, setAdding] = useState(false);  const [addError, setAddError] = useState<string | null>(null);   const identity = useIntegrationIdentity({    fallbackName: isGooglePhotosPreset ? "Google Photos" : "Google",    fallbackNamespace:      props.initialNamespace ?? (isGooglePhotosPreset ? "google_photos" : "google"),  });   const bundleDiscoveryUrls = useMemo(    () => googleBundleUrls(selectedPresetIds, customDiscoveryUrls),    [selectedPresetIds, customDiscoveryUrls],  );   const toggleBundlePreset = useCallback((presetId: string, checked: boolean) => {    setSelectedPresetIds((current: ReadonlySet<string>) => {      const next = new Set(current);      if (checked) next.add(presetId);      else next.delete(presetId);      return next;    });  }, []);   const addCustomDiscoveryUrl = useCallback((url: string) => {    setCustomDiscoveryUrls((current: readonly string[]) =>      current.includes(url) ? current : [...current, url],    );  }, []);   const removeCustomDiscoveryUrl = useCallback((url: string) => {    setCustomDiscoveryUrls((current: readonly string[]) =>      current.filter((entry: string) => entry !== url),    );  }, []);   const doAdd = useAtomSet(addGoogleBundle, { mode: "promiseExit" });   const resolvedSourceId =    slugifyNamespace(identity.namespace) || (isGooglePhotosPreset ? "google_photos" : "google");  const resolvedDisplayName =    identity.name.trim() || (isGooglePhotosPreset ? "Google Photos" : "Google");  const resolvedDescription =    descriptionDraft ??    (isGooglePhotosPreset      ? "Google Photos albums, uploads, app-created media, and selected picker media."      : "Google APIs");  const slugAlreadyExists = useSlugAlreadyExists(resolvedSourceId);  const canAdd = bundleDiscoveryUrls.length > 0 && !slugAlreadyExists;   const handleAdd = async () => {    setAdding(true);    setAddError(null);    const exit = await doAdd({      payload: {        urls: [...bundleDiscoveryUrls],        slug: resolvedSourceId,        name: resolvedDisplayName,        ...(resolvedDescription.trim().length > 0          ? { description: resolvedDescription.trim() }          : {}),        ...(baseUrl.trim().length > 0 ? { baseUrl: baseUrl.trim() } : {}),      },      reactivityKeys: integrationWriteKeys,    });    if (Exit.isFailure(exit)) {      setAddError(addIntegrationErrorMessage(exit, resolvedSourceId, "Failed to add Google"));      setAdding(false);      return;    }    props.onComplete(String(exit.value.slug));  };   return (    <div className="flex flex-1 flex-col gap-6">      <div>        <h1 className="text-xl font-semibold text-foreground">Add Google integration</h1>        <p className="mt-1 text-[13px] text-muted-foreground">          Bundle Google APIs into one integration with a shared OAuth consent.        </p>      </div>       <GoogleProductPicker        selectedPresetIds={selectedPresetIds}        onToggle={toggleBundlePreset}        customUrls={customDiscoveryUrls}        onAddCustomUrl={addCustomDiscoveryUrl}        onRemoveCustomUrl={removeCustomDiscoveryUrl}      />       <OpenApiSourceDetailsFields        title="Google"        subtitle={`${bundleDiscoveryUrls.length} Google API${          bundleDiscoveryUrls.length !== 1 ? "s" : ""        } · one shared OAuth consent`}        identity={identity}        description={resolvedDescription}        onDescriptionChange={setDescriptionDraft}        baseUrl={baseUrl}        onBaseUrlChange={setBaseUrl}        baseUrlLabel="Base URL override (optional)"        faviconIcon={GOOGLE_BUNDLE_FAVICON}        faviconUrl={baseUrl}      />       {slugAlreadyExists && !adding && <SlugCollisionAlert slug={resolvedSourceId} />}       {addError && <FormErrorAlert message={addError} />}       <FloatActions>        <Button variant="ghost" onClick={() => props.onCancel()} disabled={adding}>          Cancel        </Button>        <Button onClick={() => void handleAdd()} disabled={!canAdd} loading={adding}>          Connect Google        </Button>      </FloatActions>    </div>  );}