feat(repository): improve the fork user experience/flow/feature
+ 102
- 27
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1664245360775,
+  "_generatedAtUnix": 1664248120245,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -96,7 +96,7 @@
       "pathSource": "./app/views/repository/RepositoryCreateView.tsx"
     },
     "RepositoryDetailsView": {
-      "hash": "34fe3a6129223bed26c7a249c14909b05617b80a",
+      "hash": "07f3255d918dd9da183e98365ef33645a4f3cb6b",
       "pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
     },
     "RepositoryExploreView": {

app/services/repository/getRepository.ts
@@ -1,14 +1,14 @@
 // 1st-party
 import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
-// generated via script[generate:prisma]
-import type { Repository } from "@prisma/client";
+// app
+import { RepositoryWithForksAndParentRepo } from "../../types";
 // service
 import type { RepositoryServiceDeps } from "./types";
 
 const makeGetRepository: ServiceMethodFactory<
   RepositoryServiceDeps,
   [string, string],
-  Promise<Repository | null>
+  Promise<RepositoryWithForksAndParentRepo | null>
 > = ({ request }) => {
   return async (orgSlug, repoSlug) => {
     const parentOrg = await request.prisma.organization.findUnique({

...
@@ -22,6 +22,27 @@ const makeGetRepository: ServiceMethodFactory<
     }
 
     const repository = await request.prisma.repository.findFirst({
+      include: {
+        forkedFromRepo: {
+          select: {
+            id: true,
+            slug: true,
+            displayName: true,
+            organization: {
+              select: {
+                id: true,
+                slug: true,
+                displayName: true,
+              },
+            },
+          },
+        },
+        forks: {
+          select: {
+            _count: true,
+          },
+        },
+      },
       where: {
         slug: repoSlug,
         organization: {

app/services/repository/types.ts
@@ -14,6 +14,7 @@ import type {
   RepositoryHead,
   RepositoryLog,
   RepositoryObject,
+  RepositoryWithForksAndParentRepo,
 } from "../../types";
 
 export interface CreateRepositoryDTO {

...
@@ -49,7 +50,10 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
   canUserAccessRepository(user: User, repo: Repository): Promise<boolean>;
   createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
   forkRepository(dto: ForkRepositoryDTO): Promise<Repository>;
-  getRepository(orgSlug: string, repoSlug: string): Promise<Repository | null>;
+  getRepository(
+    orgSlug: string,
+    repoSlug: string
+  ): Promise<RepositoryWithForksAndParentRepo | null>;
   getRepositoryBranches(repository: Repository): Promise<string[]>;
   getRepositoryCommitLog(
     repository: Repository,

@@ -1,4 +1,4 @@
-import type { Prisma, GlobalRole } from "@prisma/client";
+import type { Prisma, GlobalRole, Repository } from "@prisma/client";
 
 export type AppThemeScheme = "light" | "dark";
 export type WithThemeSchemeProp = {

...
@@ -144,3 +144,19 @@ export interface RepositoryLog {
 }
 
 export type RepositoryObject = RepositoryLog;
+
+export type RepositoryWithForksAndParentRepo = Repository & {
+  forks: {
+    _count: Prisma.RepositoryCountOutputType;
+  }[];
+  forkedFromRepo: {
+    displayName: string | null;
+    id: string;
+    slug: string;
+    organization: {
+      displayName: string | null;
+      id: string;
+      slug: string;
+    };
+  } | null;
+};

app/views/repository/RepositoryDetailsView.tsx
@@ -3,7 +3,7 @@ import type { ReactView } from "@ethicdevs/react-monolith";
 // 3rd-party
 import React from "react";
 // generated via script[prisma:generate]
-import type { Organization, Repository, User } from "@prisma/client";
+import type { Organization, User } from "@prisma/client";
 // app
 import type {
   CommonProps,

...
@@ -11,6 +11,7 @@ import type {
   RepositoryFile,
   RepositoryFileContent,
   RepositoryLog,
+  RepositoryWithForksAndParentRepo,
 } from "../../types";
 import {
   Card,

...
@@ -36,7 +37,7 @@ export interface RepositoryDetailsViewProps extends CommonProps {
   parentOrg: Organization;
   path: string;
   readmeFileContent: null | RepositoryFileContent;
-  repo: Repository;
+  repo: RepositoryWithForksAndParentRepo;
   repoHead: null | RepositoryHead;
   repoFiles: RepositoryFile[];
   tags: string[];

...
@@ -60,24 +61,57 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
   return (
     <Layout {...commonProps}>
       <PageWrapper>
-        <h1>
-          <a href={`/${parentOrg.slug}`}>
-            {parentOrg.displayName || parentOrg.slug}
-          </a>
-          {" / "}
-          <a href={`/${parentOrg.slug}/${repo.slug}`}>
-            {repo.displayName || repo.slug}
-          </a>
-          {path != null && path.trim() !== "" && path !== "/"
-            ? ` / ${path}`
-            : ""}
-          {" ∙ "}
-          <span style={{ textTransform: "capitalize" }}>
-            ({repo.visibility.toLowerCase()})
-          </span>
-        </h1>
-        <a href={`/${parentOrg.slug}/${repo.slug}/fork`}>Fork it!</a>
-        <Grid.Row fluid style={{ marginTop: 32 }}>
+        <Grid.Col fluid>
+          <h1 style={{ margin: 0, marginTop: 20 }}>
+            <a href={`/${parentOrg.slug}`}>
+              {parentOrg.displayName || parentOrg.slug}
+            </a>
+            {" / "}
+            <a href={`/${parentOrg.slug}/${repo.slug}`}>
+              {repo.displayName || repo.slug}
+            </a>
+            {path != null && path.trim() !== "" && path !== "/"
+              ? ` / ${path}`
+              : ""}
+            {" ∙ "}
+            <span style={{ textTransform: "capitalize" }}>
+              ({repo.visibility.toLowerCase()})
+            </span>
+          </h1>
+          <Grid.Row nowrap fluid style={{ marginTop: 8 }}>
+            <div style={{ flex: 1 }}>
+              {repo.isFork && repo.forkedFromRepo != null ? (
+                <h5 style={{ margin: 0 }}>
+                  <span>Forked From</span>
+                  {" ∙ "}
+                  <a href={`/${repo.forkedFromRepo.organization.slug}`}>
+                    {repo.forkedFromRepo.organization.displayName ||
+                      repo.forkedFromRepo.organization.slug}
+                  </a>
+                  {" / "}
+                  <a
+                    href={`/${repo.forkedFromRepo.organization.slug}/${repo.forkedFromRepo.slug}`}
+                  >
+                    {repo.forkedFromRepo.displayName ||
+                      repo.forkedFromRepo.slug}
+                  </a>
+                </h5>
+              ) : (
+                <div />
+              )}
+            </div>
+            <Grid.Row nowrap style={{ minWidth: 90 }}>
+              <span>{repo.forks.length}</span>
+              <a
+                href={`/${parentOrg.slug}/${repo.slug}/fork`}
+                style={{ marginLeft: 8 }}
+              >
+                Fork it!
+              </a>
+            </Grid.Row>
+          </Grid.Row>
+        </Grid.Col>
+        <Grid.Row nowrap fluid style={{ marginTop: 32 }}>
           <Grid.Col fluid flex={1}>
             {repoHead == null || lastCommit == null ? (
               <Card