.ts
TypeScript
(application/typescript)
// 1st-party
import type { ReactIsland } from "@ethicdevs/react-monolith";
// 3rd-party
import React, { useCallback, useEffect, useMemo, useState } from "react";
// generated via script[generate:prisma]
import { Organization } from "@prisma/client";
// app
import { RepositoryWithParentAndForkedFromRepos } from "../types";

export interface PullRequestSourceSelectionChangeEvent {
  data: {
    selectedOrg: {
      org: Organization;
      repos: {
        [repoSlug: string]: {
          repo: RepositoryWithParentAndForkedFromRepos;
          branches: string[];
        };
      };
    };
    selectedRepo: {
      repo: RepositoryWithParentAndForkedFromRepos;
      branches: string[];
    };
    selectedBranch: string;
  };
}

export interface PullRequestSourceSelectProps {
  defaultSource: {
    org: Organization;
    repo: RepositoryWithParentAndForkedFromRepos;
    branch: string;
  };
  sources: {
    [orgSlug: string]: {
      org: Organization;
      repos: {
        [repoSlug: string]: {
          repo: RepositoryWithParentAndForkedFromRepos;
          branches: string[];
        };
      };
    };
  };
  namePrefix: "source" | "target";
  onSelectionChange?: (
    ev: PullRequestSourceSelectionChangeEvent
  ) => void | Promise<void>;
}

const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
  defaultSource,
  sources,
  namePrefix,
  onSelectionChange,
}) => {
  const [selectedOrgSlug, setSelectedOrgSlug] = useState<string>(
    defaultSource.org.slug
  );
  const [selectedRepoSlug, setSelectedRepoSlug] = useState<string>(
    defaultSource.repo.slug
  );
  const [selectedBranch, setSelectedBranch] = useState<string>(
    defaultSource.branch
  );

  const allOrganizations = useMemo(() => {
    const orgsBySlug = Object.entries(sources);
    return orgsBySlug.reduce((acc, [orgSlug, { org }]) => {
      acc = [
        ...acc,
        {
          key: orgSlug,
          value: org.displayName || org.slug,
        },
      ];
      return acc;
    }, [] as { key: string; value: string }[]);
  }, [sources]);

  const allReposBySelectedOrgSlug = useMemo(() => {
    const orgsBySlug = Object.entries(sources);
    const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
    if (org == null) return [];
    const [_selectedOrgSlug, { repos: orgRepositories }] = org;
    const reposBySlug = Object.entries(orgRepositories);
    return reposBySlug.reduce((acc, [repoSlug, { repo }]) => {
      acc = [
        ...acc,
        {
          key: repoSlug,
          value: repo.displayName || repo.slug,
        },
      ];
      return acc;
    }, [] as { key: string; value: string }[]);
  }, [sources, selectedOrgSlug]);

  const allBranchesBySelectedOrgAndRepoSlugs = useMemo(() => {
    const orgsBySlug = Object.entries(sources);
    const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
    if (org == null) return [];
    const [_selectedOrgSlug, { repos: orgRepositories }] = org;
    const reposBySlug = Object.entries(orgRepositories);
    const repo = reposBySlug.find(([_, r]) => r.repo.slug === selectedRepoSlug);
    if (repo == null) return [];
    const [_selectedRepoSlug, { branches: repoBranches }] = repo;
    return repoBranches.map((branch) => ({
      key: branch,
      value: branch,
    })) as { key: string; value: string }[];
  }, [sources, selectedOrgSlug, selectedRepoSlug]);

  const selectedOrg = useMemo(() => {
    const orgsBySlug = Object.entries(sources);
    const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
    return org != null ? org[1] : null;
  }, [sources, selectedOrgSlug]);

  const selectedRepo = useMemo(() => {
    const orgsBySlug = Object.entries(sources);
    const parentOrg = orgsBySlug.find(
      ([_, o]) => o.org.slug === selectedOrgSlug
    );
    if (parentOrg == null) return null;
    const reposBySlug = Object.entries(parentOrg[1].repos);
    const repo = reposBySlug.find(([_, r]) => r.repo.slug === selectedRepoSlug);
    return repo != null ? repo[1] : null;
  }, [sources, selectedOrgSlug, selectedRepoSlug]);

  const onOrgSlugChange = useCallback(
    (ev: React.ChangeEvent<HTMLSelectElement>) => {
      if (ev == null) return;
      if (ev.target == null) return;
      if (ev.target.value == null) return;
      setSelectedOrgSlug(ev.target.value);
    },
    [setSelectedOrgSlug]
  );

  const onRepoSlugChange = useCallback(
    (ev: React.ChangeEvent<HTMLSelectElement>) => {
      if (ev == null) return;
      if (ev.target == null) return;
      if (ev.target.value == null) return;
      setSelectedRepoSlug(ev.target.value);
    },
    [setSelectedRepoSlug]
  );

  const onBranchChange = useCallback(
    (ev: React.ChangeEvent<HTMLSelectElement>) => {
      if (ev == null) return;
      if (ev.target == null) return;
      if (ev.target.value == null) return;
      setSelectedBranch(ev.target.value);
    },
    [setSelectedBranch]
  );

  const keyValueToOptionMapFn = useMemo(
    () =>
      ({ key, value }: { key: string; value: string }) =>
        (
          <option key={key} value={key}>
            {value}
          </option>
        ),
    []
  );

  useEffect(() => {
    if (selectedOrg != null && selectedRepo != null && selectedBranch != null) {
      if (
        onSelectionChange != null &&
        typeof onSelectionChange === "function" &&
        selectedOrg != null &&
        selectedRepo != null
      ) {
        onSelectionChange({
          data: {
            selectedOrg,
            selectedRepo,
            selectedBranch,
          },
        });
      }
    }
  }, [onSelectionChange, selectedOrg, selectedRepo, selectedBranch]);

  return (
    <div>
      <select
        name={`${namePrefix}_parent_org_slug`}
        value={selectedOrgSlug}
        onChange={onOrgSlugChange}
      >
        {allOrganizations.map(keyValueToOptionMapFn)}
      </select>
      {" / "}
      <select
        name={`${namePrefix}_repository_slug`}
        value={selectedRepoSlug}
        onChange={onRepoSlugChange}
      >
        {allReposBySelectedOrgSlug.map(keyValueToOptionMapFn)}
      </select>
      {" / "}
      <select
        name={`${namePrefix}_repository_${
          namePrefix === "source" ? "from" : "dest"
        }_branch`}
        value={selectedBranch}
        onChange={onBranchChange}
      >
        {allBranchesBySelectedOrgAndRepoSlugs.map(keyValueToOptionMapFn)}
      </select>
    </div>
  );
};

PullRequestSourceSelect.displayName = "PullRequestSourceSelect";
export default PullRequestSourceSelect;