GitFOSS
William Nemenchaimprove user experience
a7cddb1 (parent 791e7f6)4/7/2024, 5:07:26 AM
.ts
TypeScript
(application/typescript)
// 1st-party
import type { ReactIsland } from "@ethicdevs/react-monolith";
// 3rd-party
import React, { useCallback, useMemo, useState } from "react";
// generated via script[generate:prisma]
import type { Organization, PullRequest, Repository } from "@prisma/client";
// app
import type {
  AppThemeScheme,
  RepositoryFileDiff,
  RepositoryWithParentAndForkedFromRepos,
} from "../types";
import { buildRouteLink } from "../utils/shared";
import { AppRoute } from "../routes.defs";
import { Button, Card, Grid, IslandWrapper } from "../components";
// app islands
import RepositoryFilesDiffsList from "./RepositoryFilesDiffsList";
import PullRequestSourceSelect, {
  PullRequestSourceSelection,
  PullRequestSourceSelectionChangeEvent,
} from "./PullRequestSourceSelect";

export enum PullRequestFormState {
  CONFIGURE = "configure",
  COMPARE = "compare",
  DETAILS = "input_details",
  ERROR = "error",
}

export interface RepositoryPullRequestCreateFormPropsCommon {
  errorMessage?: string | null;
}

export interface RepositoryPullRequestTarget {
  parentOrg: Organization;
  repo: Repository;
  branch: string;
}
export type RepositoryPullRequestCreateFormPropsByState<
  S extends PullRequestFormState = PullRequestFormState
> = S extends PullRequestFormState.CONFIGURE
  ? {
      sources: {
        [orgSlug: string]: {
          org: Organization;
          repos: {
            [repoKey: string]: {
              repo: RepositoryWithParentAndForkedFromRepos;
              branches: string[];
            };
          };
        };
      };
      target: RepositoryPullRequestTarget;
    }
  : S extends PullRequestFormState.COMPARE
  ? {
      fileDiffs: RepositoryFileDiff[];
      source: RepositoryPullRequestTarget;
      target: RepositoryPullRequestTarget;
    }
  : S extends PullRequestFormState.DETAILS
  ? {
      canCurrentUserSubmitPullRequest: boolean;
      pullRequest: PullRequest;
    }
  : S extends PullRequestFormState.ERROR
  ? {
      errorMessage: string;
    }
  : never;

export type RepositoryPullRequestCreateFormVariant =
  | {
      state: PullRequestFormState.CONFIGURE;
      data: RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.CONFIGURE>;
    }
  | {
      state: PullRequestFormState.COMPARE;
      data: RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.COMPARE>;
    }
  | {
      state: PullRequestFormState.DETAILS;
      data: RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.DETAILS>;
    }
  | {
      state: PullRequestFormState.ERROR;
      data: RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.ERROR>;
    };

export interface RepositoryPullRequestCreateFormProps
  extends RepositoryPullRequestCreateFormPropsCommon {
  parentOrgSlug: string;
  repoSlug: string;
  themeScheme: AppThemeScheme;
  variant: RepositoryPullRequestCreateFormVariant;
}

// PullRequestFormState.CONFIGURE
const isConfigureState = (
  s: PullRequestFormState
): s is PullRequestFormState.CONFIGURE =>
  typeof s !== "undefined" && s != null && s === PullRequestFormState.CONFIGURE;
const isConfigureStateData = (
  s: PullRequestFormState,
  i: unknown
): i is RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.CONFIGURE> =>
  typeof i !== "undefined" && i != null && s === PullRequestFormState.CONFIGURE;
// PullRequestFormState.COMPARE
const isCompareState = (
  s: PullRequestFormState
): s is PullRequestFormState.COMPARE =>
  typeof s !== "undefined" && s != null && s === PullRequestFormState.COMPARE;
const isCompareStateData = (
  s: PullRequestFormState,
  i: unknown
): i is RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.COMPARE> =>
  typeof i !== "undefined" && i != null && s === PullRequestFormState.COMPARE;
// PullRequestFormState.DETAILS
const isDetailsState = (
  s: PullRequestFormState
): s is PullRequestFormState.DETAILS =>
  typeof s !== "undefined" && s != null && s === PullRequestFormState.DETAILS;
const isDetailsStateData = (
  s: PullRequestFormState,
  i: unknown
): i is RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.DETAILS> =>
  typeof i !== "undefined" && i != null && s === PullRequestFormState.DETAILS;
// PullRequestFormState.ERROR
const isErrorState = (
  s: PullRequestFormState
): s is PullRequestFormState.ERROR =>
  typeof s !== "undefined" && s != null && s === PullRequestFormState.ERROR;
const isErrorStateData = (
  s: PullRequestFormState,
  i: unknown
): i is RepositoryPullRequestCreateFormPropsByState<PullRequestFormState.ERROR> =>
  typeof i !== "undefined" && i != null && s === PullRequestFormState.ERROR;

const RepositoryPullRequestCreateForm: ReactIsland<
  RepositoryPullRequestCreateFormProps
> = ({ parentOrgSlug, repoSlug, themeScheme, variant: { state, data } }) => {
  const [selectedSource, setSelectedSource] =
    useState<null | PullRequestSourceSelection>(null);
  const [selectedTarget, setSelectedTarget] =
    useState<null | PullRequestSourceSelection>(null);

  const onSourceSelectionChange = useCallback(
    (ev: PullRequestSourceSelectionChangeEvent) => {
      setSelectedSource(ev.data);
    },
    [setSelectedSource]
  );

  const onTargetSelectionChange = useCallback(
    (ev: PullRequestSourceSelectionChangeEvent) => {
      setSelectedTarget(ev.data);
    },
    [setSelectedTarget]
  );

  const isCompareButtonEnabled = useMemo(() => {
    const isSameRepoInSameOrg =
      selectedSource != null &&
      selectedTarget != null &&
      selectedSource.selectedRepo.repo.organization.slug ===
        selectedTarget.selectedRepo.repo.organization.slug &&
      selectedSource.selectedRepo.repo.slug ===
        selectedTarget.selectedRepo.repo.slug;
    return !!(
      selectedSource != null &&
      selectedTarget != null &&
      isSameRepoInSameOrg === false &&
      selectedSource.selectedBranch != null &&
      selectedTarget.selectedBranch != null
    );
  }, [selectedSource, selectedTarget]);

  isCompareButtonEnabled;

  // PullRequestFormState.CONFIGURE
  if (isConfigureState(state) && isConfigureStateData(state, data)) {
    return (
      <Grid.Col fluid nowrap>
        <form
          method={"POST"}
          action={buildRouteLink(
            AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION,
            { orgSlug: parentOrgSlug, repoSlug }
          )}
          style={{ width: "100%" }}
        >
          <Card themeScheme={themeScheme}>
            <input type={"hidden"} name={"state_from"} value={state} />
            <input
              type={"hidden"}
              name={"state_dest"}
              value={PullRequestFormState.COMPARE}
            />
            <Grid.Row fluid alignItems={"center"} gap={8}>
              <Grid.Row nowrap fluid gap={8} alignItems={"center"}>
                <label style={{ display: "block", minWidth: 60 }}>
                  Target:
                </label>
                <div
                  data-islandid={`${PullRequestSourceSelect.name}$$1`}
                  style={{ width: "100%" }}
                >
                  <PullRequestSourceSelect
                    themeScheme={themeScheme}
                    namePrefix={"target"}
                    defaultSource={{
                      org: Object.values(data.sources)[0].org,
                      repo: Object.values(
                        Object.values(data.sources)[0].repos
                      )[0].repo,
                      branch: Object.values(
                        Object.values(data.sources)[0].repos
                      )[0].branches[0],
                    }}
                    sources={data.sources}
                    onSelectionChange={onTargetSelectionChange}
                  />
                </div>
              </Grid.Row>
              <Grid.Row
                // flex={0.2}
                justifyContent={"center"}
                alignItems={"center"}
                style={{ margin: "0 8px", width: 80 }}
              >
                {/* <span>&lt;--</span> */}
              </Grid.Row>
              <Grid.Row nowrap fluid gap={8} alignItems={"center"}>
                <label style={{ display: "block", minWidth: 60 }}>
                  Source:
                </label>
                <div
                  data-islandid={`${PullRequestSourceSelect.name}$$0`}
                  style={{ width: "100%" }}
                >
                  <PullRequestSourceSelect
                    themeScheme={themeScheme}
                    namePrefix={"source"}
                    defaultSource={{
                      org: data.target.parentOrg,
                      repo: data.target
                        .repo as RepositoryWithParentAndForkedFromRepos,
                      branch: data.target.branch,
                    }}
                    sources={data.sources}
                    onSelectionChange={onSourceSelectionChange}
                  />
                </div>
              </Grid.Row>
              <Button
                style={{ justifySelf: "flex-end" }}
                // disabled={isCompareButtonEnabled === false}
                type={"submit"}
              >
                Compare
              </Button>
            </Grid.Row>
          </Card>
        </form>
      </Grid.Col>
    );
  }
  // PullRequestFormState.COMPARE
  if (isCompareState(state) && isCompareStateData(state, data)) {
    if (data.fileDiffs.length <= 0) {
      return (
        <Grid.Col fluid nowrap>
          <h1>Nothing to compare from... 🤔</h1>
          <p>
            The source and the target you've just selected seems to be equals.
          </p>
          <p>
            Try comparing two branches that have some differences before you can
            create a pull request from the produced diff.
          </p>
          <a
            href={buildRouteLink(AppRoute.REPOSITORY_PULL_REQUEST_CREATE, {
              orgSlug: parentOrgSlug,
              repoSlug,
            })}
          >
            Try again
          </a>
        </Grid.Col>
      );
    }

    return (
      <Grid.Col fluid nowrap>
        <form
          method={"POST"}
          action={buildRouteLink(
            AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION,
            { orgSlug: parentOrgSlug, repoSlug }
          )}
          style={{ width: "100%" }}
        >
          <input type={"hidden"} name={"state_from"} value={state} />
          <input
            type={"hidden"}
            name={"state_dest"}
            value={PullRequestFormState.DETAILS}
          />
          <input
            type={"hidden"}
            name={"source_parent_org_slug"}
            value={data.source.parentOrg.slug}
          />
          <input
            type={"hidden"}
            name={"source_repository_slug"}
            value={data.source.repo.slug}
          />
          <input
            type={"hidden"}
            name={"source_repository_from_branch"}
            value={data.source.branch}
          />
          <input
            type={"hidden"}
            name={"target_parent_org_slug"}
            value={data.target.parentOrg.slug}
          />
          <input
            type={"hidden"}
            name={"target_repository_slug"}
            value={data.target.repo.slug}
          />
          <input
            type={"hidden"}
            name={"target_repository_dest_branch"}
            value={data.target.branch}
          />
          <Grid.Row fluid>
            <Grid.Col fluid>
              <Card themeScheme={themeScheme} style={{ width: "100%" }}>
                <Grid.Col fluid>
                  <label htmlFor={"summary"}>Summary</label>
                  <input
                    type={"text"}
                    name={"summary"}
                    style={{ width: "100%" }}
                  />
                </Grid.Col>
                <Grid.Col fluid style={{ marginTop: 8 }}>
                  <label htmlFor={"description"}>Description</label>
                  <textarea
                    name={"description"}
                    style={{
                      width: "100%",
                      maxWidth: "100%",
                      minWidth: "100%",
                      minHeight: 180,
                    }}
                  ></textarea>
                </Grid.Col>
                <Grid.Col
                  fluid
                  style={{ marginTop: 8 }}
                  alignItems={"flex-end"}
                >
                  <button type={"submit"}>Create Pull Request</button>
                </Grid.Col>
              </Card>
            </Grid.Col>
            <Grid.Col flex={0.3} style={{ marginLeft: 16 }}>
              <Card themeScheme={themeScheme} style={{ width: "100%" }}>
                <span></span>
              </Card>
            </Grid.Col>
          </Grid.Row>
        </form>
        <IslandWrapper
          data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
          style={{ marginTop: 16 }}
        >
          <RepositoryFilesDiffsList
            filesDiffs={data.fileDiffs}
            themeScheme={themeScheme}
            orgSlug={data.source.parentOrg.slug}
            repoSlug={data.source.repo.slug}
            commitHash={""}
          />
        </IslandWrapper>
      </Grid.Col>
    );
  }
  // PullRequestFormState.DETAILS
  if (isDetailsState(state) && isDetailsStateData(state, data)) {
    return (
      <Grid.Col fluid nowrap>
        <form
          method={"POST"}
          action={buildRouteLink(
            AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION,
            { orgSlug: parentOrgSlug, repoSlug }
          )}
        >
          <input type={"hidden"} name={"state_from"} value={state} />
          <button type={"submit"}>Submit the Pull Request</button>
        </form>
        <div>
          <pre>
            <code>{JSON.stringify(data, null, 2)}</code>
          </pre>
        </div>
      </Grid.Col>
    );
  }
  // PullRequestFormState.ERROR
  if (isErrorState(state) && isErrorStateData(state, data)) {
    const { errorMessage } = data;
    return (
      <Grid.Col fluid nowrap>
        <h1>Woops, an error occurred:</h1>
        <div>{errorMessage}</div>
      </Grid.Col>
    );
  }
  return null;
};

RepositoryPullRequestCreateForm.displayName = "RepositoryPullRequestCreateForm";
export default RepositoryPullRequestCreateForm;

GitFOSS • v0.2.0 (#421408f) • MIT License