diff --git a/packages/frontend/.env b/packages/frontend/.env
new file mode 100644
index 00000000..5b03837d
--- /dev/null
+++ b/packages/frontend/.env
@@ -0,0 +1 @@
+REACT_APP_GQL_SERVER_URL = 'http://localhost:8000/graphql'
diff --git a/packages/frontend/.eslintrc.json b/packages/frontend/.eslintrc.json
index f57b9c16..96d5591b 100644
--- a/packages/frontend/.eslintrc.json
+++ b/packages/frontend/.eslintrc.json
@@ -17,5 +17,7 @@
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
- "rules": {}
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off"
+ }
}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index fedec4b1..bcefa1e5 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -11,6 +11,7 @@
"@types/node": "^16.18.68",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
+ "assert": "^2.1.0",
"date-fns": "^3.0.1",
"downshift": "^8.2.3",
"luxon": "^3.4.4",
diff --git a/packages/frontend/src/context/GQLClientContext.tsx b/packages/frontend/src/context/GQLClientContext.tsx
new file mode 100644
index 00000000..d71db3dc
--- /dev/null
+++ b/packages/frontend/src/context/GQLClientContext.tsx
@@ -0,0 +1,24 @@
+import React, { createContext, useContext, ReactNode } from 'react';
+import { GQLClient } from 'gql-client';
+
+const GQLClientContext = createContext({} as GQLClient);
+
+export const GQLClientProvider = ({
+ client,
+ children,
+}: {
+ children: ReactNode;
+ client: GQLClient;
+}) => (
+
+ {children}
+
+);
+
+export const useGQLClient = () => {
+ const client = useContext(GQLClientContext);
+ if (!client) {
+ throw new Error('useGQLClient must be used within a GQLClientProvider');
+ }
+ return client;
+};
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index af5a9a1c..6317b830 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -1,21 +1,32 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
+import assert from 'assert';
+import { Toaster } from 'react-hot-toast';
+import { GQLClient } from 'gql-client';
import { ThemeProvider } from '@material-tailwind/react';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
-import { Toaster } from 'react-hot-toast';
+import { GQLClientProvider } from './context/GQLClientContext';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
+
+const gqlEndpoint = process.env.REACT_APP_GQL_SERVER_URL;
+assert(gqlEndpoint, 'GQL server URL not provided');
+
+const gqlClient = new GQLClient({ gqlEndpoint });
+
root.render(
-
-
+
+
+
+
,
);
diff --git a/packages/frontend/src/pages/index.tsx b/packages/frontend/src/pages/index.tsx
index 204b7a84..5045a837 100644
--- a/packages/frontend/src/pages/index.tsx
+++ b/packages/frontend/src/pages/index.tsx
@@ -1,12 +1,49 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Typography, Chip } from '@material-tailwind/react';
import ProjectCard from '../components/projects/ProjectCard';
import projectsDetail from '../assets/projects.json';
+import { useGQLClient } from '../context/GQLClientContext';
const Projects = () => {
+ const client = useGQLClient();
+ const [projects, setProjects] = useState([]);
+
+ useEffect(() => {
+ const fetchOrganization = async () => {
+ const res = await client.getOrganizations();
+
+ // Note: select first organization as organization switching not yet implemented
+ const projects = res.organizations[0].projects;
+
+ const updatedProjects = projects.map((project: any) => {
+ return {
+ ...project,
+ // TODO: populate empty fields
+ icon: '',
+ title: '',
+ organization: '',
+ url: '',
+ domain: null,
+ createdBy: '',
+ source: '',
+ // TODO: populate from github API
+ latestCommit: {
+ message: '',
+ createdAt: '',
+ branch: '',
+ },
+ };
+ });
+
+ setProjects(updatedProjects);
+ };
+
+ fetchOrganization();
+ }, [client]);
+
return (
@@ -29,9 +66,10 @@ const Projects = () => {
- {projectsDetail.map((project, key) => {
- return
;
- })}
+ {projects.length !== 0 &&
+ projects.map((project, key) => {
+ return
;
+ })}
);
diff --git a/packages/gql-client/src/gql-client.ts b/packages/gql-client/src/gql-client.ts
index 6b0856ea..b2f3627b 100644
--- a/packages/gql-client/src/gql-client.ts
+++ b/packages/gql-client/src/gql-client.ts
@@ -1,6 +1,6 @@
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
-import { getUser } from './gql-queries';
+import { getUser, getOrganizations } from './gql-queries';
export interface GraphQLConfig {
gqlEndpoint: string;
@@ -23,4 +23,12 @@ export class GQLClient {
return data;
}
+
+ async getOrganizations () : Promise {
+ const { data } = await this.client.query({
+ query: getOrganizations
+ });
+
+ return data;
+ }
}
diff --git a/packages/gql-client/src/gql-queries.ts b/packages/gql-client/src/gql-queries.ts
index 93b1a31b..565d7954 100644
--- a/packages/gql-client/src/gql-queries.ts
+++ b/packages/gql-client/src/gql-queries.ts
@@ -11,3 +11,34 @@ query {
}
}
`;
+
+export const getOrganizations = gql`
+query {
+ organizations {
+ projects {
+ id
+ owner {
+ id
+ }
+ deployments {
+ id
+ }
+ name
+ repository
+ prodBranch
+ description
+ template
+ framework
+ webhooks
+ members {
+ id
+ }
+ environmentVariables {
+ id
+ }
+ createdAt
+ updatedAt
+ }
+ }
+}
+`;
diff --git a/yarn.lock b/yarn.lock
index 8e0d40ff..f9b1a8ba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4226,6 +4226,17 @@ asap@~2.0.6:
resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
+assert@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
ast-types-flow@^0.0.8:
version "0.0.8"
resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz"
@@ -7927,7 +7938,7 @@ ipaddr.js@^2.0.1:
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz"
integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
-is-arguments@^1.1.1:
+is-arguments@^1.0.4, is-arguments@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
@@ -8043,7 +8054,7 @@ is-generator-fn@^2.0.0:
resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
-is-generator-function@^1.0.10:
+is-generator-function@^1.0.10, is-generator-function@^1.0.7:
version "1.0.10"
resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz"
integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
@@ -8084,6 +8095,14 @@ is-module@^1.0.0:
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz"
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
+is-nan@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz"
@@ -8211,7 +8230,7 @@ is-text-path@^1.0.1:
dependencies:
text-extensions "^1.0.0"
-is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9:
+is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9:
version "1.1.12"
resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz"
integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==
@@ -13592,6 +13611,17 @@ util.promisify@~1.0.0:
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
+util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
utila@~0.4:
version "0.4.0"
resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz"
@@ -13950,7 +13980,7 @@ which-collection@^1.0.1:
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
-which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9:
+which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2, which-typed-array@^1.1.9:
version "1.1.13"
resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz"
integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==