refactor(pull_requests): make the "PullRequestSourceSelect" to work
+ 186
- 92
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1665328026365,
+  "_generatedAtUnix": 1665337338713,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -16,7 +16,7 @@
       "pathSourceMap": "./public/.islands/InstantRouterIndicator.bundle.js.map"
     },
     "PullRequestSourceSelect": {
-      "hash": "0bf2070d8f31e61476388b8b753d1979ccda9f5d",
+      "hash": "7639a6f9840bd79911c72c89687f7cd541ca6940",
       "pathSource": "./app/islands/PullRequestSourceSelect.tsx",
       "pathBundle": "./public/.islands/PullRequestSourceSelect.bundle.js",
       "pathSourceMap": "./public/.islands/PullRequestSourceSelect.bundle.js.map"

...
@@ -64,7 +64,7 @@
       "pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
     },
     "RepositoryPullRequestCreateForm": {
-      "hash": "8b7d27a4a368329ad810309272b7ff848395f1d2",
+      "hash": "19e66103e6c815f8770f6291759f9b557c5125bb",
       "pathSource": "./app/islands/RepositoryPullRequestCreateForm.tsx",
       "pathBundle": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js.map"

app/controllers/repository/getRepositoryPullRequestCreateView.ts
@@ -39,7 +39,6 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
   const orgService = makeOrganizationService({ request });
   const repoService = makeRepositoryService({ request });
   const usersService = makeUsersService({ request });
-  usersService;
 
   const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
   const repo = await repoService.getRepository(orgSlug, repoSlug);

...
@@ -49,17 +48,18 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
   }
 
   const branches = await repoService.getRepositoryBranches(repo);
+
   const currentUserForks = await repoService.getCurrentUserRepositoryForks(
     repo
   );
 
-  const repos = await currentUserForks.reduce(
+  const currentUserForksRepos = await currentUserForks.reduce(
     async (accP, r) => {
       let acc = await accP;
       acc = {
         ...acc,
-        [r.slug]: {
-          repo: r as any,
+        [`${r.organization.slug}/${r.slug}`]: {
+          repo: r,
           branches: await repoService.getRepositoryBranches(r),
         },
       };

...
@@ -74,14 +74,10 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
   );
 
   // Inject PR target's repository first.
-  // const [personalOrg] = await usersService.getUserOrganizations(
-  //   request.session.data.curr_user_uid,
-  //   true // personalOnly
-  // );
-
-  //  const personalRepos = {
-  //    [repo.slug]: { repo: repo as any, branches },
-  //  };
+  const [personalOrg] = await usersService.getUserOrganizations(
+    request.session.data.curr_user_uid,
+    true // personalOnly
+  );
 
   let variant: RepositoryPullRequestCreateFormVariant;
   if (from_branch == null) {

...
@@ -89,21 +85,23 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
       state: PullRequestFormState.CONFIGURE,
       data: {
         sources: {
-          // In case user has forks on its own, add its personal org (where forks **should** be)
-          // ...(currentUserForks.length >= 1 && {
-          //   [personalOrg.slug]: {
-          //     org: personalOrg,
-          //     repos: personalRepos,
-          //   },
-          // }),
           // target repo's source
           [parentOrg.slug]: {
             org: parentOrg,
             repos: {
-              [repo.slug]: { repo: repo as any, branches },
-              ...repos,
+              [`${parentOrg.slug}/${repo.slug}`]: {
+                repo: { ...repo, organization: parentOrg },
+                branches,
+              },
             },
           },
+          // In case user has forks on its own, add its personal org (where forks **should** be)
+          ...(currentUserForks.length >= 1 && {
+            [personalOrg.slug]: {
+              org: personalOrg,
+              repos: currentUserForksRepos,
+            },
+          }),
         },
         target: {
           branch: from_branch || Const.PRIMARY_BRANCH_REF,

app/islands/PullRequestSourceSelect.tsx
@@ -7,23 +7,25 @@ import { Organization } from "@prisma/client";
 // app
 import { RepositoryWithParentAndForkedFromRepos } from "../types";
 
-export interface PullRequestSourceSelectionChangeEvent {
-  data: {
-    selectedOrg: {
-      org: Organization;
-      repos: {
-        [repoSlug: string]: {
-          repo: RepositoryWithParentAndForkedFromRepos;
-          branches: string[];
-        };
+export interface PullRequestSourceSelection {
+  selectedOrg: {
+    org: Organization;
+    repos: {
+      [repoSlug: string]: {
+        repo: RepositoryWithParentAndForkedFromRepos;
+        branches: string[];
       };
     };
-    selectedRepo: {
-      repo: RepositoryWithParentAndForkedFromRepos;
-      branches: string[];
-    };
-    selectedBranch: string;
   };
+  selectedRepo: {
+    repo: RepositoryWithParentAndForkedFromRepos;
+    branches: string[];
+  };
+  selectedBranch: string;
+}
+
+export interface PullRequestSourceSelectionChangeEvent {
+  data: PullRequestSourceSelection;
 }
 
 export interface PullRequestSourceSelectProps {

...
@@ -36,7 +38,7 @@ export interface PullRequestSourceSelectProps {
     [orgSlug: string]: {
       org: Organization;
       repos: {
-        [repoSlug: string]: {
+        [repoKey: string]: {
           repo: RepositoryWithParentAndForkedFromRepos;
           branches: string[];
         };

...
@@ -67,11 +69,11 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
 
   const allOrganizations = useMemo(() => {
     const orgsBySlug = Object.entries(sources);
-    return orgsBySlug.reduce((acc, [orgSlug, { org }]) => {
+    return orgsBySlug.reduce((acc, [_, { org }]) => {
       acc = [
         ...acc,
         {
-          key: orgSlug,
+          key: org.slug,
           value: org.displayName || org.slug,
         },
       ];

...
@@ -85,11 +87,11 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
     if (org == null) return [];
     const [_selectedOrgSlug, { repos: orgRepositories }] = org;
     const reposBySlug = Object.entries(orgRepositories);
-    return reposBySlug.reduce((acc, [repoSlug, { repo }]) => {
+    return reposBySlug.reduce((acc, [_, { repo }]) => {
       acc = [
         ...acc,
         {
-          key: repoSlug,
+          key: repo.slug,
           value: repo.displayName || repo.slug,
         },
       ];

...
@@ -102,14 +104,17 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
     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);
+    const reposByKey = Object.entries(orgRepositories);
+    const repo = reposByKey.find(
+      (r) =>
+        r[1].repo.organization.slug === selectedOrgSlug &&
+        r[1].repo.slug === selectedRepoSlug
+    );
     if (repo == null) return [];
-    const [_selectedRepoSlug, { branches: repoBranches }] = repo;
-    return repoBranches.map((branch) => ({
+    return repo[1].branches.map((branch) => ({
       key: branch,
       value: branch,
-    })) as { key: string; value: string }[];
+    }));
   }, [sources, selectedOrgSlug, selectedRepoSlug]);
 
   const selectedOrg = useMemo(() => {

...
@@ -125,18 +130,76 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
     );
     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;
+    const repo = reposBySlug.find(
+      (r) =>
+        r[1].repo.organization.slug === selectedOrgSlug &&
+        r[1].repo.slug === selectedRepoSlug
+    );
+    if (repo == null) return null;
+    return repo[1];
   }, [sources, selectedOrgSlug, selectedRepoSlug]);
 
+  const selectedBranchResult = useMemo(() => {
+    const orgsBySlug = Object.entries(sources);
+    const parentOrg = orgsBySlug.find(
+      ([_, o]) => o.org.slug === selectedOrgSlug
+    );
+    if (parentOrg == null) return selectedBranch;
+    const reposBySlug = Object.entries(parentOrg[1].repos);
+    const repo = reposBySlug.find(
+      (r) =>
+        r[1].repo.organization.slug === selectedOrgSlug &&
+        r[1].repo.slug === selectedRepoSlug
+    );
+    if (repo == null) return selectedBranch;
+    const { branches } = repo[1];
+    return selectedBranch || branches[0];
+  }, [sources, selectedOrgSlug, selectedRepoSlug, selectedBranch]);
+
+  const emitSelectionChangeEvent = useCallback(() => {
+    if (
+      onSelectionChange != null &&
+      typeof onSelectionChange === "function" &&
+      selectedOrg != null &&
+      selectedRepo != null &&
+      selectedBranchResult != null
+    ) {
+      onSelectionChange({
+        data: {
+          selectedOrg,
+          selectedRepo,
+          selectedBranch: selectedBranchResult,
+        },
+      });
+    }
+  }, [onSelectionChange, selectedOrg, selectedRepo, selectedBranchResult]);
+
   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);
+      const orgSlug = ev.target.value;
+      setSelectedOrgSlug(orgSlug);
+      if (orgSlug in sources) {
+        const { repos } = sources[orgSlug];
+        if (Object.keys(repos).length >= 1) {
+          const { repo, branches } = Object.values(repos)[0];
+          setSelectedRepoSlug(repo.slug);
+          if (branches.length >= 1) {
+            setSelectedBranch(branches[0]);
+          }
+        }
+      }
+      emitSelectionChangeEvent();
     },
-    [setSelectedOrgSlug]
+    [
+      emitSelectionChangeEvent,
+      sources,
+      setSelectedOrgSlug,
+      setSelectedRepoSlug,
+      setSelectedBranch,
+    ]
   );
 
   const onRepoSlugChange = useCallback(

...
@@ -144,9 +207,23 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
       if (ev == null) return;
       if (ev.target == null) return;
       if (ev.target.value == null) return;
-      setSelectedRepoSlug(ev.target.value);
+      const repoSlug = ev.target.value;
+      setSelectedRepoSlug(repoSlug);
+      if (repoSlug in sources[selectedOrgSlug].repos) {
+        const { branches } = sources[selectedOrgSlug].repos[repoSlug];
+        if (branches.length >= 1) {
+          setSelectedBranch(branches[0]);
+        }
+      }
+      emitSelectionChangeEvent();
     },
-    [setSelectedRepoSlug]
+    [
+      emitSelectionChangeEvent,
+      sources,
+      selectedOrgSlug,
+      setSelectedRepoSlug,
+      setSelectedBranch,
+    ]
   );
 
   const onBranchChange = useCallback(

...
@@ -155,15 +232,16 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
       if (ev.target == null) return;
       if (ev.target.value == null) return;
       setSelectedBranch(ev.target.value);
+      emitSelectionChangeEvent();
     },
-    [setSelectedBranch]
+    [emitSelectionChangeEvent, setSelectedBranch]
   );
 
   const keyValueToOptionMapFn = useMemo(
     () =>
-      ({ key, value }: { key: string; value: string }) =>
+      ({ key, value }: { key: string; value: string }, idx: number) =>
         (
-          <option key={key} value={key}>
+          <option key={[key, idx].join(":")} value={key}>
             {value}
           </option>
         ),

...
@@ -171,23 +249,8 @@ const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
   );
 
   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]);
+    emitSelectionChangeEvent();
+  }, []);
 
   return (
     <div>

app/islands/RepositoryPullRequestCreateForm.tsx
@@ -1,7 +1,7 @@
 // 1st-party
 import type { ReactIsland } from "@ethicdevs/react-monolith";
 // 3rd-party
-import React, { useCallback } from "react";
+import React, { useCallback, useMemo, useState } from "react";
 // generated via script[generate:prisma]
 import type { Organization, PullRequest, Repository } from "@prisma/client";
 // app

...
@@ -16,6 +16,7 @@ import { IslandWrapper } from "../components/IslandWrapper.styled";
 // app islands
 import RepositoryFilesDiffsList from "./RepositoryFilesDiffsList";
 import PullRequestSourceSelect, {
+  PullRequestSourceSelection,
   PullRequestSourceSelectionChangeEvent,
 } from "./PullRequestSourceSelect";
 

...
@@ -43,7 +44,7 @@ export type RepositoryPullRequestCreateFormPropsByState<
         [orgSlug: string]: {
           org: Organization;
           repos: {
-            [repoSlug: string]: {
+            [repoKey: string]: {
               repo: RepositoryWithParentAndForkedFromRepos;
               branches: string[];
             };

...
@@ -138,18 +139,44 @@ const isErrorStateData = (
 
 const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFormProps> =
   ({ parentOrgSlug, repoSlug, themeScheme, variant: { state, data } }) => {
-    /*const [selectedSourceOrgSlug, setSelectedSourceOrgSlug] =
-      useState<null | string>(null);*/
+    const [selectedSource, setSelectedSource] =
+      useState<null | PullRequestSourceSelection>(null);
+    const [selectedTarget, setSelectedTarget] =
+      useState<null | PullRequestSourceSelection>(null);
 
     const onSourceSelectionChange = useCallback(
       (ev: PullRequestSourceSelectionChangeEvent) => {
-        console.warn(
-          `Selected: ${ev.data.selectedOrg.org.slug} / ${ev.data.selectedRepo.repo.slug} / ${ev.data.selectedBranch}`
-        );
+        setSelectedSource(ev.data);
       },
-      []
+      [setSelectedSource]
     );
 
+    const onTargetSelectionChange = useCallback(
+      (ev: PullRequestSourceSelectionChangeEvent) => {
+        setSelectedTarget(ev.data);
+      },
+      [setSelectedTarget]
+    ); //
+
+    const isCompareButtonEnabled = useMemo(() => {
+      console.log("selectedSource:", selectedSource);
+      console.log("selectedTarget:", selectedTarget);
+      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]);
+
     // PullRequestFormState.CONFIGURE
     if (isConfigureState(state) && isConfigureStateData(state, data)) {
       return (

...
@@ -167,9 +194,9 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
                 value={PullRequestFormState.COMPARE}
               />
               <Grid.Row fluid alignItems={"center"}>
-                <div data-islandid={`${PullRequestSourceSelect.name}$$0`}>
+                <div data-islandid={`${PullRequestSourceSelect.name}$$1`}>
                   <PullRequestSourceSelect
-                    namePrefix={"source"}
+                    namePrefix={"target"}
                     defaultSource={{
                       org: Object.values(data.sources)[0].org,
                       repo: Object.values(

...
@@ -180,15 +207,15 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
                       )[0].branches[0],
                     }}
                     sources={data.sources}
-                    onSelectionChange={onSourceSelectionChange}
+                    onSelectionChange={onTargetSelectionChange}
                   />
                 </div>
                 <div style={{ margin: "0 8px" }}>
                   <span>vs.</span>
                 </div>
-                <div data-islandid={`${PullRequestSourceSelect.name}$$1`}>
+                <div data-islandid={`${PullRequestSourceSelect.name}$$0`}>
                   <PullRequestSourceSelect
-                    namePrefix={"target"}
+                    namePrefix={"source"}
                     defaultSource={{
                       org: Object.values(data.sources)[0].org,
                       repo: Object.values(

...
@@ -202,7 +229,11 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
                     onSelectionChange={onSourceSelectionChange}
                   />
                 </div>
-                <button type={"submit"} style={{ marginLeft: 8 }}>
+                <button
+                  disabled={isCompareButtonEnabled === false}
+                  type={"submit"}
+                  style={{ marginLeft: 8 }}
+                >
                   Compare
                 </button>
               </Grid.Row>

...
@@ -216,13 +247,15 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
       if (data.fileDiffs.length <= 0) {
         return (
           <Grid.Col fluid nowrap>
-            <h1>Nothing to request Pull from... 🤔</h1>
+            <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 diff.
+              can create a pull request from the produced diff.
             </p>
-            <a href={`/${parentOrgSlug}/${repoSlug}/pulls/new`}>Go back</a>
+            <a href={`/${parentOrgSlug}/${repoSlug}/pulls/new`}>Try again</a>
           </Grid.Col>
         );
       }

...
@@ -304,7 +337,7 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
               </Grid.Col>
               <Grid.Col flex={0.3} style={{ marginLeft: 16 }}>
                 <Card themeScheme={themeScheme} style={{ width: "100%" }}>
-                  <span>fuck the police</span>
+                  <span></span>
                 </Card>
               </Grid.Col>
             </Grid.Row>