GitFOSS
.ts
TypeScript
(application/typescript)
// 1st-party
import type { ReactView } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// generated via script[generate:prisma]
import {
  PullRequestState,
  type Organization,
  type PullRequest,
  type User,
} from "@prisma/client";
// app
import type {
  CommonProps,
  RepositoryFileDiff,
  RepositoryObject,
  RepositoryWithForkedFromRepo,
} from "../../types";
import { AppRoute } from "../../routes.defs";
import { buildRouteLink } from "../../utils/shared";
import {
  Card,
  Grid,
  InlineCode,
  IslandWrapper,
  Layout,
  MarkdownToJsx,
  PageWrapper,
  TextInput,
  TextArea,
} from "../../components";
// app islands
import RepositoryFilesDiffsList from "../../islands/RepositoryFilesDiffsList";
import RepositoryHero from "../../islands/RepositoryHero";

export interface RepositoryPullRequestDetailsViewProps extends CommonProps {
  filesDiffs: RepositoryFileDiff[];
  lastCommit: RepositoryObject | null;
  pullRequest: PullRequest;
  pullRequestAuthor: User;
  sourceParentOrg: Organization;
  sourceRepo: RepositoryWithForkedFromRepo;
  targetParentOrg: Organization;
  targetRepo: RepositoryWithForkedFromRepo;
  isCurrentUserAllowedToMerge?: boolean;
}

const RepositoryPullRequestDetailsView: ReactView<
  RepositoryPullRequestDetailsViewProps
> = ({
  commonProps,
  filesDiffs,
  lastCommit,
  pullRequest: pr,
  pullRequestAuthor: prAuthor,
  sourceParentOrg,
  sourceRepo,
  targetParentOrg: parentOrg,
  targetRepo: repo,
  isCurrentUserAllowedToMerge = false,
}) => {
  const totalDiff = filesDiffs.reduce(
    (acc, diff) => {
      acc = {
        ...acc,
        additions: (acc?.additions || 0) + diff.additions,
        deletions: (acc?.deletions || 0) + diff.deletions,
      };
      return acc;
    },
    { additions: 0, deletions: 0 },
  );

  if (pr.state !== PullRequestState.OPEN) {
    isCurrentUserAllowedToMerge = false;
  }

  return (
    <Layout
      {...commonProps}
      showDrawerPrimary
      orgSlug={parentOrg.slug}
      repoSlug={repo.slug}
    >
      <PageWrapper>
        <IslandWrapper data-islandid={`${RepositoryHero.name}$$0`}>
          <RepositoryHero
            forkedFromRepo={repo.forkedFromRepo}
            forksCount={repo.forks.length}
            parentOrg={parentOrg}
            path={`Pull Request / #${pr.uid}`}
            repo={repo}
          />
        </IslandWrapper>
        <Grid.Col fluid style={{ marginTop: 24 }}>
          <Grid.Col key={pr.id} fluid>
            <Grid.Row fluid alignItems="center" gap={16}>
              <h1 style={{ margin: 0, marginTop: 8 }}>{pr.summary}</h1>
              <span>
                <InlineCode themeScheme={commonProps.themeScheme}>
                  {`[${pr.state}]`}
                </InlineCode>
              </span>
            </Grid.Row>

            <Grid.Row
              fluid
              nowrap
              gap={16}
              style={{ opacity: 0.67, marginTop: 8 }}
            >
              <a
                href={buildRouteLink(
                  AppRoute.USER_DETAILS,
                  {
                    username: prAuthor.username,
                  },
                  { encodeURIComponent: false },
                )}
              >
                {prAuthor.displayName || prAuthor.username}
              </a>
              {isCurrentUserAllowedToMerge && (
                <>
                  <a
                    href={buildRouteLink(
                      AppRoute.REPOSITORY_PULL_REQUEST_UPDATE_ACTION,
                      {
                        orgSlug: parentOrg.slug,
                        repoSlug: repo.slug,
                        pullUid: pr.uid,
                      },
                    )}
                  >
                    Edit PR
                  </a>
                  <a
                    href={buildRouteLink(
                      AppRoute.REPOSITORY_PULL_REQUEST_DELETE_ACTION,
                      {
                        orgSlug: parentOrg.slug,
                        repoSlug: repo.slug,
                        pullUid: pr.uid,
                      },
                    )}
                  >
                    Delete PR
                  </a>
                </>
              )}
            </Grid.Row>
            <span style={{ opacity: 0.67, marginTop: 8 }}>
              {"wants to merge branch "}
              <InlineCode themeScheme={commonProps.themeScheme}>
                {pr.sourceBranch}
              </InlineCode>
              {" from repository "}
              <InlineCode themeScheme={commonProps.themeScheme}>
                {`${sourceParentOrg.slug}/${sourceRepo.slug}`}
              </InlineCode>
              {" (source) "}
            </span>
            <span style={{ opacity: 0.67, marginTop: 4 }}>
              {" into branch "}
              <InlineCode themeScheme={commonProps.themeScheme}>
                {pr.targetBranch}
              </InlineCode>
              {" of repository "}
              <InlineCode themeScheme={commonProps.themeScheme}>
                {`${parentOrg.slug}/${repo.slug}`}
              </InlineCode>
              {" (target) "}
            </span>
            <Grid.Row
              fluid
              alignItems={"center"}
              style={{ opacity: 0.67, marginTop: 8 }}
            >
              {new Date(pr.createdAt).getTime() <=
                new Date(pr.updatedAt).getTime() && (
                <span>opened on {new Date(pr.createdAt).toLocaleString()}</span>
              )}
              {((pr.closedAt == null &&
                new Date(pr.updatedAt).getTime() >
                  new Date(pr.createdAt).getTime()) ||
                (pr.closedAt != null &&
                  new Date(pr.updatedAt).getTime() <
                    new Date(pr.closedAt).getTime())) && (
                <span>
                  updated on {new Date(pr.updatedAt).toLocaleString()}
                </span>
              )}
              {pr.closedAt != null && (
                <span>
                  closed on
                  {new Date(pr.closedAt).toLocaleString()}
                </span>
              )}
            </Grid.Row>
          </Grid.Col>
          {isCurrentUserAllowedToMerge && (
            <Card
              style={{ width: "100%", padding: 8, marginTop: 16 }}
              themeScheme={commonProps.themeScheme}
            >
              <Grid.Col fluid style={{ marginTop: 8 }}>
                <form
                  style={{ width: "100%" }}
                  method={"POST"}
                  action={buildRouteLink(
                    AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION,
                    {
                      orgSlug: parentOrg.slug,
                      repoSlug: repo.slug,
                      pullUid: pr.uid,
                    },
                  )}
                >
                  <Grid.Col fluid nowrap gap={8}>
                    <TextInput
                      themeScheme={commonProps.themeScheme}
                      name={"merge_summary"}
                      type={"text"}
                      placeholder={
                        "Enter a short description of the merged code..."
                      }
                      style={{ width: "100%" }}
                    />
                    <TextArea
                      themeScheme={commonProps.themeScheme}
                      name={"merge_message"}
                      placeholder={
                        "Describe what this merge will bring when merged in target repository..."
                      }
                      style={{ width: "100%", minHeight: 180 }}
                    ></TextArea>
                    <Grid.Row
                      fluid
                      nowrap
                      justifyContent={"flex-end"}
                      gap={8}
                      alignItems={"center"}
                      style={{ marginTop: 8 }}
                    >
                      <button type={"submit"} name={"merge_default"}>
                        Merge
                      </button>
                      <button type={"submit"} name={"merge_squash"}>
                        Merge w/ Squash
                      </button>
                      <button type={"submit"} name={"merge_rebase"}>
                        Merge w/ Rebase
                      </button>
                    </Grid.Row>
                  </Grid.Col>
                </form>
              </Grid.Col>
            </Card>
          )}
          <Card
            style={{ width: "100%", padding: 8, marginTop: 8 }}
            themeScheme={commonProps.themeScheme}
          >
            <Grid.Row
              fluid
              nowrap
              alignItems={"center"}
              style={{ marginBottom: 8 }}
              gap={16}
            >
              <div style={{ color: "rgb(43, 176, 90)" }}>
                <strong>+</strong> <span>{totalDiff.additions}</span>
              </div>
              <div style={{ color: "rgb(215, 44, 44)" }}>
                <strong>-</strong> <span>{totalDiff.deletions}</span>
              </div>
            </Grid.Row>
            <MarkdownToJsx
              themeScheme={commonProps.themeScheme}
              markdown={
                pr.textMd == null || pr.textMd.trim() === ""
                  ? "> &lt;no_description_yet /&gt;"
                  : pr.textMd
              }
            />
          </Card>
          <Grid.Col
            gap={8}
            fluid
            data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
            style={{ marginTop: 8, padding: 0 }}
          >
            <RepositoryFilesDiffsList
              filesDiffs={filesDiffs}
              themeScheme={commonProps.themeScheme}
              orgSlug={parentOrg.slug}
              repoSlug={repo.slug}
              commitHash={lastCommit != null ? lastCommit.commit : "HEAD"}
            />
          </Grid.Col>
        </Grid.Col>
      </PageWrapper>
    </Layout>
  );
};

RepositoryPullRequestDetailsView.displayName =
  "RepositoryPullRequestDetailsView";
export default RepositoryPullRequestDetailsView;