refactor(dashboard): show current user projects on dashboard
+ 144
- 35
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1663773368816,
+  "_generatedAtUnix": 1663788290484,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -9,6 +9,12 @@
       "pathBundle": "./public/.islands/InstantRouterIndicator.bundle.js",
       "pathSourceMap": "./public/.islands/InstantRouterIndicator.bundle.js.map"
     },
+    "RepositoriesList": {
+      "hash": "d0e1a580d6857fbf98a08bf5a6242ec5902dac9b",
+      "pathSource": "./app/islands/RepositoriesList.tsx",
+      "pathBundle": "./public/.islands/RepositoriesList.bundle.js",
+      "pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"
+    },
     "RepositoryCreateForm": {
       "hash": "13bc29f8548d0df057f56a78bb6f2f6b31cb7d31",
       "pathSource": "./app/islands/RepositoryCreateForm.tsx",

...
@@ -30,7 +36,7 @@
   },
   "views": {
     "HomeView": {
-      "hash": "f1f68abb235c063ad79a1921fc4be2e8f8b2597a",
+      "hash": "3e321a2058675d89de2582ff3758f3287ac69a01",
       "pathSource": "./app/views/HomeView.tsx"
     },
     "InternalErrorView": {

...
@@ -38,7 +44,7 @@
       "pathSource": "./app/views/InternalErrorView.tsx"
     },
     "DashboardView": {
-      "hash": "9b287b43216a2a41bb961737801dfd950945b6af",
+      "hash": "316530648e98734fd1e983c8dcf9062d772363fb",
       "pathSource": "./app/views/auth/DashboardView.tsx"
     },
     "LoginView": {

...
@@ -66,7 +72,7 @@
       "pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
     },
     "RepositoryExploreView": {
-      "hash": "06685120a2cdbcb6671ac0436054a3b1345594ef",
+      "hash": "416d8fe270478274dc12f6ac09e75bb789842ec3",
       "pathSource": "./app/views/repository/RepositoryExploreView.tsx"
     }
   }

app/controllers/auth/getDashboardView.ts
@@ -17,10 +17,12 @@ const getDashboardView: ReqHandler = async (request, reply) => {
 
   const usersService = makeUsersService({ request });
   const currentUser = await usersService.getUserById(curr_user_uid);
+  const repositories = await usersService.getUserRepositories(curr_user_uid);
 
   const reqHandler = reply.makeRequestHandler(request, reply);
   return reqHandler<DashboardViewProps>(DashboardView.name, {
     currentUser,
+    repositories,
   });
 };
 

app/controllers/repository/getRepositoryExploreView.ts
@@ -1,6 +1,6 @@
 // 1st-party
 import type { ReqHandler } from "@ethicdevs/react-monolith";
-// app
+// app services
 import { makeRepositoryService } from "../../services/repository";
 // app views
 import RepositoryExploreView, {

new file
app/islands/RepositoriesList.tsx
@@ -0,0 +1,40 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+// generated via script[generate:prisma]
+import { Organization, Repository } from "@prisma/client";
+
+export interface RepositoriesListProps {
+  repositories: Array<Repository & { parentOrg: Organization }>;
+}
+
+const RepositoriesList: ReactIsland<RepositoriesListProps> = ({
+  repositories,
+}) => {
+  return (
+    <>
+      {repositories.map((repo) => (
+        <div key={repo.id}>
+          <h1>
+            <a href={`/${repo.parentOrg.slug}/${repo.slug}`}>
+              {repo.parentOrg.displayName || repo.parentOrg.slug}
+              {" / "}
+              {repo.displayName || repo.slug}
+              {" ∙ "}
+              <span style={{ textTransform: "capitalize" }}>
+                ({repo.visibility.toLowerCase()})
+              </span>
+            </a>
+          </h1>
+          <div>
+            <p>{repo.shortDescription}</p>
+          </div>
+        </div>
+      ))}
+    </>
+  );
+};
+
+RepositoriesList.displayName = "RepositoriesList";
+export default RepositoriesList;

app/services/codeAnalysis/getLinguistFileInfos.ts
@@ -23,13 +23,13 @@ const makeGetLinguistFileInfos: ServiceMethodFactory<
       };
     }
 
-    const language =
+    let language =
       content != null
         ? deps.languageDetect.contents(path, content)
         : deps.languageDetect.filename(path);
 
     if (language == null || language in deps.languagesMap === false) {
-      throw new Error(`Invalid language: null.`);
+      language = "Shell";
     }
 
     const languageInfos = deps.languagesMap[language];

new file
app/services/user/getUserRepositories.ts
@@ -0,0 +1,45 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { Organization, Repository } from "@prisma/client";
+// app
+import type { UsersServiceDeps } from "./types";
+
+const getUserRepositories: ServiceMethodFactory<
+  UsersServiceDeps,
+  [string],
+  Promise<(Repository & { parentOrg: Organization })[]>
+> = ({ request }) => {
+  return async (userId) => {
+    const userRepos = await request.prisma.repository.findMany({
+      include: {
+        organization: true,
+      },
+      where: {
+        OR: [
+          {
+            organization: {
+              ownerId: userId,
+            },
+          },
+          {
+            organization: {
+              memberships: {
+                some: {
+                  userId,
+                },
+              },
+            },
+          },
+        ],
+      },
+    });
+
+    return userRepos.map(({ organization: parentOrg, ...repo }) => ({
+      ...repo,
+      parentOrg,
+    }));
+  };
+};
+
+export default getUserRepositories;

app/services/user/index.ts
@@ -8,6 +8,7 @@ import { default as makeGetUserById } from "./getUserById";
 import { default as makeGetUserByUsername } from "./getUserByUsername";
 import { default as makeGetUserOrganizationMemberships } from "./getUserOrganizationMemberships";
 import { default as makeGetUserOrganizations } from "./getUserOrganizations";
+import { default as makeGetUserRepositories } from "./getUserRepositories";
 
 export const makeUsersService = makeService<UsersServiceAPI, UsersServiceDeps>({
   getUserByEmailAddress: makeGetUserByEmailAddress,

...
@@ -15,4 +16,5 @@ export const makeUsersService = makeService<UsersServiceAPI, UsersServiceDeps>({
   getUserByUsername: makeGetUserByUsername,
   getUserOrganizationMemberships: makeGetUserOrganizationMemberships,
   getUserOrganizations: makeGetUserOrganizations,
+  getUserRepositories: makeGetUserRepositories,
 });

app/services/user/types.ts
@@ -3,8 +3,12 @@ import { ServiceApiContract } from "@ethicdevs/react-monolith";
 // 3rd-party
 import { FastifyRequest } from "fastify";
 // generated via script[generate:prisma]
-import { Organization, OrganizationMembership, User } from "@prisma/client";
-// app
+import {
+  Organization,
+  OrganizationMembership,
+  Repository,
+  User,
+} from "@prisma/client";
 
 export interface UsersServiceAPI extends ServiceApiContract {
   getUserById(userId: string): Promise<User | null>;

...
@@ -14,6 +18,9 @@ export interface UsersServiceAPI extends ServiceApiContract {
     userId: string
   ): Promise<OrganizationMembership[]>;
   getUserOrganizations(userId: string): Promise<Organization[]>;
+  getUserRepositories(
+    userId: string
+  ): Promise<(Repository & { parentOrg: Organization })[]>;
 }
 
 export interface UsersServiceDeps {

app/views/HomeView.tsx
@@ -17,8 +17,10 @@ const HomeView: ReactView<HomeViewProps> = (props) => {
     <Layout {...commonProps}>
       <PageWrapper>
         <StyledButtonsRow>
-          <ButtonAnchor href={"/auth/register"}>Get Started</ButtonAnchor>
-          <ButtonAnchor href={"/features"}>Learn More</ButtonAnchor>
+          <ButtonAnchor href={"/auth/register"}>Create an Account</ButtonAnchor>
+          <ButtonAnchor href={"/repo/explore"}>
+            Explore Repositories
+          </ButtonAnchor>
         </StyledButtonsRow>
       </PageWrapper>
     </Layout>

...
@@ -28,11 +30,10 @@ const HomeView: ReactView<HomeViewProps> = (props) => {
 const StyledButtonsRow = styled.div`
   display: flex;
   flex-flow: row wrap;
-  align-items: stretch;
+  align-items: center;
   justify-content: center;
   gap: 16px;
   margin-top: 24px;
-  max-width: 440px;
   width: 100%;
 `;
 

app/views/auth/DashboardView.tsx
@@ -3,20 +3,34 @@ import type { ReactView } from "@ethicdevs/react-monolith";
 // 3rd-party
 import React from "react";
 // generated via script[generate:prisma]
-import type { User } from "@prisma/client";
+import type { Organization, Repository, User } from "@prisma/client";
 // app
 import type { CommonProps } from "../../types";
 import { Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoriesList from "../../islands/RepositoriesList";
 
 export interface DashboardViewProps extends CommonProps {
   currentUser: User;
+  repositories: (Repository & { parentOrg: Organization })[];
 }
 
-const DashboardView: ReactView<DashboardViewProps> = ({ commonProps }) => {
+const DashboardView: ReactView<DashboardViewProps> = ({
+  commonProps,
+  currentUser,
+  repositories,
+}) => {
   return (
     <Layout {...commonProps}>
       <PageWrapper>
-        <h1>Hey, welcome!</h1>
+        <h1>Hey {currentUser.displayName || currentUser.username}, welcome!</h1>
+        <h2>Repositories you own and/or contribute to</h2>
+        <div
+          data-islandid={`${RepositoriesList.name}$$0`}
+          style={{ width: "100%" }}
+        >
+          <RepositoriesList repositories={repositories} />
+        </div>
       </PageWrapper>
     </Layout>
   );

app/views/repository/RepositoryExploreView.tsx
@@ -7,6 +7,8 @@ import type { Organization, Repository } from "@prisma/client";
 // app
 import type { CommonProps } from "../../types";
 import { Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoriesList from "../../islands/RepositoriesList";
 
 export interface RepositoryExploreViewProps extends CommonProps {
   repositories: (Repository & { parentOrg: Organization })[];

...
@@ -19,24 +21,14 @@ const RepositoryExploreView: ReactView<RepositoryExploreViewProps> = ({
   return (
     <Layout {...commonProps}>
       <PageWrapper>
-        {repositories.map((repo) => (
-          <div key={repo.id}>
-            <h1>
-              <a href={`/${repo.parentOrg.slug}/${repo.slug}`}>
-                {repo.parentOrg.displayName || repo.parentOrg.slug}
-                {" / "}
-                {repo.displayName || repo.slug}
-                {" ∙ "}
-                <span style={{ textTransform: "capitalize" }}>
-                  ({repo.visibility.toLowerCase()})
-                </span>
-              </a>
-            </h1>
-            <div>
-              <p>{repo.shortDescription}</p>
-            </div>
-          </div>
-        ))}
+        <h1>Explore public repositories</h1>
+        <h2>Discover your next project to contribute to!</h2>
+        <div
+          data-islandid={`${RepositoriesList.name}$$0`}
+          style={{ width: "100%" }}
+        >
+          <RepositoriesList repositories={repositories} />
+        </div>
       </PageWrapper>
     </Layout>
   );

@@ -17,7 +17,7 @@
     "resolveJsonModule": true,
     "rootDir": ".",
     "sourceMap": true,
-    "target": "es5",
+    "target": "es2015",
 
     // Strict Type-Checking Options
     "strict": true,