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==