From 988822a5ccb55ca05c37b46189d56eea4439024f Mon Sep 17 00:00:00 2001 From: ccai89 Date: Wed, 15 Dec 2021 12:05:41 -0500 Subject: [PATCH 1/5] merging --- demo/client/src/components/CacheTab.tsx | 147 + demo/client/src/components/CacheView.tsx | 38 + demo/client/src/components/ClientTab.tsx | 258 ++ demo/client/src/components/InputEditor.tsx | 81 + demo/client/src/components/Metrics.tsx | 38 + demo/client/src/components/NavButton.tsx | 28 + demo/client/src/components/OutputEditor.tsx | 36 + demo/client/src/components/PrimaryNavBar.tsx | 52 + demo/client/src/components/QueryTab.tsx | 64 + demo/client/src/components/ServerTab.tsx | 73 + demo/client/src/components/Settings.tsx | 78 + demo/client/src/containers/Devtool Demo | 156 + demo/client/src/containers/Devtool.jsx | 10 + demo/client/src/containers/Header.jsx | 5 +- demo/client/src/containers/Main.jsx | 2 + demo/client/src/containers/Team.jsx | 247 +- .../QUELL-headshot w border-Chang.png | Bin 0 -> 623945 bytes .../QUELL-headshot w border-Josh.png | Bin 0 -> 678050 bytes .../QUELL-headshot w border-Nayan.png | Bin 79264 -> 1136181 bytes .../QUELL-headshot w border-Robert.png | Bin 0 -> 829061 bytes .../QUELL-headshot w border-Tash.png | Bin 82269 -> 833688 bytes .../QUELL-headshot w border-Tim.png | Bin 62546 -> 757206 bytes demo/client/src/index.scss | 29 +- demo/package-lock.json | 2582 ++++++++++++++--- demo/package.json | 21 +- demo/tsconfig.json | 23 + demo/webpack.config.js | 21 +- 27 files changed, 3518 insertions(+), 471 deletions(-) create mode 100644 demo/client/src/components/CacheTab.tsx create mode 100644 demo/client/src/components/CacheView.tsx create mode 100644 demo/client/src/components/ClientTab.tsx create mode 100644 demo/client/src/components/InputEditor.tsx create mode 100644 demo/client/src/components/Metrics.tsx create mode 100644 demo/client/src/components/NavButton.tsx create mode 100644 demo/client/src/components/OutputEditor.tsx create mode 100644 demo/client/src/components/PrimaryNavBar.tsx create mode 100644 demo/client/src/components/QueryTab.tsx create mode 100644 demo/client/src/components/ServerTab.tsx create mode 100644 demo/client/src/components/Settings.tsx create mode 100644 demo/client/src/containers/Devtool Demo create mode 100644 demo/client/src/containers/Devtool.jsx create mode 100644 demo/client/src/images/profile_pics/QUELL-headshot w border-Chang.png create mode 100644 demo/client/src/images/profile_pics/QUELL-headshot w border-Josh.png create mode 100644 demo/client/src/images/profile_pics/QUELL-headshot w border-Robert.png create mode 100644 demo/tsconfig.json diff --git a/demo/client/src/components/CacheTab.tsx b/demo/client/src/components/CacheTab.tsx new file mode 100644 index 00000000..6fa77c1f --- /dev/null +++ b/demo/client/src/components/CacheTab.tsx @@ -0,0 +1,147 @@ +/* eslint-disable react/react-in-jsx-scope */ +import React, { useState, useEffect } from 'react'; +import NavButton from './NavButton'; +import CacheView from './CacheView'; +import SearchImg from '../assets/search.png'; +import beautify from 'json-beautify'; + +const CacheTab = ({ serverAddress, redisRoute, handleClearCache }) => { + //use state to store data from redis server + const [redisStats, setRedisStats] = useState({}); + const [redisKeys, setRedisKeys] = useState([]); + const [redisValues, setRedisValues] = useState([]); + const [activeTab, setActiveTab] = useState('server'); + + const fetchRedisInfo = () => { + fetch(`${serverAddress}${redisRoute}`) + .then((response) => response.json()) + .then((data) => { + console.log('redis info: ', data) + if (data.redisStats) setRedisStats(data.redisStats); + if (data.redisKeys) setRedisKeys(data.redisKeys); + if (data.redisValues) setRedisValues(data.redisValues); + }) + .catch((error) => + console.log('error fetching from redis endpoint: ', error) + ); + }; + + useEffect(() => { + fetchRedisInfo(); + }, []); + + const genTable = (title) => { + const output = []; + for (let key in redisStats[title]) { + output.push( +
+
+ {redisStats[title][key].name} +
+
+ {redisStats[title][key].value} +
+
+ ); + } + return output; + }; + + const [filter, setFilter] = useState('') + const activeStyle = { backgroundColor: '#444' }; + const handleFilter = (e) => { + setFilter(e.target.value); + } + + return ( +
+ {/* title */} + {/* Cache Server */} + +
+ Redis Database Status + Quell Server Cache +
+ +
+
+
+ + + + + + + +
+ +
+ {activeTab === 'server' &&
{genTable('server')}
} + + {activeTab === 'client' &&
{genTable('client')}
} + + {activeTab === 'memory' &&
{genTable('memory')}
} + + {activeTab === 'stats' &&
{genTable('stats')}
} +
+ + +
+ +
+
+ search + +
+ +
+
+
+ ); +}; + +export default CacheTab; diff --git a/demo/client/src/components/CacheView.tsx b/demo/client/src/components/CacheView.tsx new file mode 100644 index 00000000..370fd722 --- /dev/null +++ b/demo/client/src/components/CacheView.tsx @@ -0,0 +1,38 @@ +import { useState, useEffect } from 'react'; + +const CacheView = ({ redisKeys, redisValues, filteredVal } = props) => { + const getFilteredCache = () => { + const temp = []; + let i = 0; + if (redisValues.length > 0) { + while (i < redisKeys.length && i < redisValues.length) { + if (redisKeys[i].includes(filteredVal)) + temp.push( +
+ {redisKeys[i]} + {redisValues[i]} +
+ ) + i++; + } + } + else if (redisKeys.length > 0) { + redisKeys.forEach((el, i) => { + temp.push( +

{el}

+ ) + }) + } else temp.push( +

No keys or values returned. Your Redis cache may be empty, or the specified endpoint may not be configured to return keys and/or values. See the Quell docs for configuration instructions.

+ ) + return temp; + } + + return ( + <> + {getFilteredCache()} + + ) +} + +export default CacheView \ No newline at end of file diff --git a/demo/client/src/components/ClientTab.tsx b/demo/client/src/components/ClientTab.tsx new file mode 100644 index 00000000..07a6b0d3 --- /dev/null +++ b/demo/client/src/components/ClientTab.tsx @@ -0,0 +1,258 @@ +import React, { useState, useEffect, useMemo} from 'react'; +import { useTable } from 'react-table'; +import Metrics from './Metrics'; +import SplitPane from 'react-split-pane'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/theme/material-darker.css'; +import 'codemirror/theme/xq-light.css'; +import 'codemirror'; +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/mode'; +import beautify from 'json-beautify'; +import NavButton from './NavButton'; +import { getResponseStatus } from '../helpers/getResponseStatus'; +import { getOperationNames } from '../helpers/parseQuery'; + +const ClientTab = ({ graphQLRoute, clientAddress, clientRequests } = props) => { + const [clickedRowData, setClickedRowData] = useState({}); + const [activeRow, setActiveRow] = useState(-1); + + return ( +
+
+ Client Quell Requests +
+
+ +
+ +
+ {activeRow > -1 ? ( + + ) : ( +
+ 0 + ? clientRequests[clientRequests.length - 1].time.toFixed(2) + : 0 + } + fetchTimeInt={ + clientRequests.length > 0 + ? clientRequests.map((request) => request.time) + : [0] + } + /> +
+ )} +
+
+
+ ); +}; + +const RequestDetails = ({ clickedRowData } = props) => { + const [activeTab, setActiveTab] = useState('request'); + const activeStyle = { + backgroundColor: '#444', + color: '#bbb', + }; + + return ( +
+
+ + < NavButton + text={'request'} + activeTab={activeTab} + setActiveTab={setActiveTab} + altText={'Request Headers'} + altClass={'clientNavButton'} + /> + + < NavButton + text={'response'} + activeTab={activeTab} + setActiveTab={setActiveTab} + altText={'Response Headers'} + altClass={'clientNavButton'} + /> + + < NavButton + text={'data'} + activeTab={activeTab} + setActiveTab={setActiveTab} + altText={'Response Data'} + altClass={'clientNavButton'} + /> + +
+
+ {activeTab === 'request' && ( + <> + {/*
Request Headers
*/} + {clickedRowData.request.headers.map((header, index) => ( +

+ {header.name}: {header.value} +

+ ))} + + )} + {activeTab === 'response' && ( + <> + {/*
Response Headers
*/} + {clickedRowData.response.headers.map((header, index) => ( +

+ {header.name}: {header.value} +

+ ))} + + )} +
+ {activeTab === 'data' && ( + <> + + + )} + +
+ ); +}; + +const NetworkRequestTable = ({ + clientRequests, + setClickedRowData, + setActiveRow, + activeRow, +} = props) => { + const handleRowClick = (cell) => { + setClickedRowData(cell.row.original); + }; + + const columns = useMemo( + () => [ + { + id: 'number', + Header: '#', + accessor: (row, index) => index + 1, + }, + { + // maybe instead of query type, use `graphql-tag` to display name of queried table/document + id: 'query-type', + Header: 'Operation Type(s)', + // accessor: (row) => Object.keys(JSON.parse(row.request.postData.text)), + accessor: row => getOperationNames(row) + }, + { + id: 'url', + Header: 'URL', + accessor: (row) => row.request.url, + }, + { + id: 'status', + Header: 'Status', + accessor: (row) => getResponseStatus(row), + }, + { + id: 'size', + Header: 'Size (kB)', + accessor: (row) => (row.response.content.size / 1000).toFixed(2), + }, + { + id: 'time', + Header: 'Time (ms)', + accessor: (row) => row.time.toFixed(2), + }, + ], + [] + ); + + // React Table suggests memoizing table data as best practice, to reduce computation + // in populating table, but this prevents live updating on new client requests + // const data = useMemo(() => [...clientRequests], []); + const data = clientRequests; + + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = + useTable({ columns, data }); + + return ( + <> +
+ + + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + + ))} + + ))} + + + {rows.map((row) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => { + return ( + + ); + })} + + ); + })} + +
+ {column.render('Header')} +
{ + console.log(cell.row.id); + if (activeRow !== cell.row.id) + setActiveRow(cell.row.id); + else setActiveRow(-1); + handleRowClick(cell); + }} + > +
{cell.render('Cell')}
+
+
+ + ); +}; + +export default ClientTab; \ No newline at end of file diff --git a/demo/client/src/components/InputEditor.tsx b/demo/client/src/components/InputEditor.tsx new file mode 100644 index 00000000..f85f7752 --- /dev/null +++ b/demo/client/src/components/InputEditor.tsx @@ -0,0 +1,81 @@ +/* eslint-disable react/prop-types */ +import React, { useState } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/theme/material-darker.css'; +import 'codemirror/theme/xq-light.css'; +import 'codemirror'; +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/mode'; + +const InputEditor = (props) => { + const [defaultText, setText] = useState( + '# Enter GraphQL query here\n' + ); + const [queryTimes, setQueryTimes] = useState([0]); + + const handleClickSubmit = () => { + let startT = performance.now(); + const query = props.queryString; + const address = `${props.serverAddress}${props.graphQLRoute}`; + fetch(address, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: query, + operationName: undefined, + variables: null, + }), + }) + .then((response) => response.json()) + .then((data) => props.setResults(data)) + .then(() => props.logNewTime(performance.now() - startT)) + .catch((err) => props.setResults(err)); + }; + + return ( +
+ { + setText(value); + }} + // sends Query to parent componet to be processed by + onChange={(editor, data, value) => { + props.setQueryString(value); + }} + /> +
+ + +
+
+ ); +}; + +export default InputEditor; diff --git a/demo/client/src/components/Metrics.tsx b/demo/client/src/components/Metrics.tsx new file mode 100644 index 00000000..0b47cffb --- /dev/null +++ b/demo/client/src/components/Metrics.tsx @@ -0,0 +1,38 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useState, useEffect } from 'react'; +import Trend from 'react-trend'; + +const Metrics = (props) => { + const { + fetchTime, // time to fetch request + fetchTimeInt, // array of time values at each point in fetching and caching + } = props; + const avgFetchTime = fetchTimeInt[0] ? (fetchTimeInt.reduce((a:number, b:number) => a+b, 0)/fetchTimeInt.length).toFixed(2) + " ms": " - ms"; + + return ( +
+
Metrics:
+
+
Latest query/mutation time:
+
{fetchTime ? fetchTime + " ms" :" - ms"}
+
Average cache time: {avgFetchTime}
+
+
+

Speed Graph:

+ =250 ? Number(`${window.innerHeight}`)-220 : 30} + // width={Number(window.innerWidth / 5)} + className="trend" + data={fetchTimeInt} + gradient={['#1feaea','#ffd200', '#f72047']} + radius={0.9} + strokeWidth={2.2} + strokeLinecap={'round'} + /> +
+
+ ); +}; + +export default Metrics; \ No newline at end of file diff --git a/demo/client/src/components/NavButton.tsx b/demo/client/src/components/NavButton.tsx new file mode 100644 index 00000000..1d5cbfcd --- /dev/null +++ b/demo/client/src/components/NavButton.tsx @@ -0,0 +1,28 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable react/react-in-jsx-scope */ +const NavButton = ({ + text, + activeTab, + setActiveTab, + altText, + altClass + } = props) => { + + return ( + + ) +} + +export default NavButton; \ No newline at end of file diff --git a/demo/client/src/components/OutputEditor.tsx b/demo/client/src/components/OutputEditor.tsx new file mode 100644 index 00000000..a850886e --- /dev/null +++ b/demo/client/src/components/OutputEditor.tsx @@ -0,0 +1,36 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { useState, useEffect } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/theme/material-darker.css'; +import 'codemirror/theme/xq-light.css'; +import beautify from 'json-beautify'; + +const OutputEditor = ({results}) => { + const [output, setOutput] = useState('# GraphQL query results') + + useEffect(() => { + if (Object.keys(results).length > 0) { + setOutput(beautify(results, null, 2, 80)); + } + }, [results]) + + + return( + { + // console.log(value); + // }} + + /> + ); +}; + +export default OutputEditor; \ No newline at end of file diff --git a/demo/client/src/components/PrimaryNavBar.tsx b/demo/client/src/components/PrimaryNavBar.tsx new file mode 100644 index 00000000..5c41114b --- /dev/null +++ b/demo/client/src/components/PrimaryNavBar.tsx @@ -0,0 +1,52 @@ +import NavButton from './NavButton'; + +const PrimaryNavBar = ({ + activeTab, + setActiveTab, + Logo, + graphQL_field, + server_field, + redis_field +} = props) => { + + const goToSettings = () => { + setActiveTab('settings'); + } + + return ( + + ); +}; + +export default PrimaryNavBar; diff --git a/demo/client/src/components/QueryTab.tsx b/demo/client/src/components/QueryTab.tsx new file mode 100644 index 00000000..9d9e9856 --- /dev/null +++ b/demo/client/src/components/QueryTab.tsx @@ -0,0 +1,64 @@ +/* eslint-disable react/react-in-jsx-scope */ +/* eslint-disable react/prop-types */ +import {useState} from 'react'; +import InputEditor from './InputEditor'; +import OutputEditor from './OutputEditor'; +import Metrics from './Metrics'; +import SplitPane from 'react-split-pane'; + +const QueryTab = ({ + clientAddress, + serverAddress, + graphQLRoute, + queryString, + setQueryString, + setResults, + schema, + clearCacheRoute, + results + } = props) => { + + // storing response times for each query as an array + const [queryResponseTime, setQueryResponseTime] = useState([]); + + // grabbing the time to query results and rounding to two digits + const logNewTime = (recordedTime: number) => { + setQueryResponseTime( + queryResponseTime.concat(Number(recordedTime.toFixed(2))) + ); + }; + + return ( +
+
+ +
+ +
+ +
+ +
+
+
+
+ +
+
+ ) +} + +export default QueryTab; \ No newline at end of file diff --git a/demo/client/src/components/ServerTab.tsx b/demo/client/src/components/ServerTab.tsx new file mode 100644 index 00000000..4253d5a1 --- /dev/null +++ b/demo/client/src/components/ServerTab.tsx @@ -0,0 +1,73 @@ +import { useState, useEffect } from 'react'; +import InputEditor from './InputEditor'; +import OutputEditor from './OutputEditor'; +import Metrics from './Metrics'; +import SplitPane from 'react-split-pane'; + + +const ServerTab = ({ + clientAddress, + serverAddress, + graphQLRoute, + queryString, + setQueryString, + setResults, + schema, + results, + handleClearCache + } = props) => { + + // storing response times for each query as an array + const [queryResponseTime, setQueryResponseTime] = useState([]); + + // grabbing the time to query results and rounding to two digits + const logNewTime = (recordedTime: number) => { + setQueryResponseTime( + queryResponseTime.concat(Number(recordedTime.toFixed(2))) + ); + }; + + return ( +
+ +
+ +
+ +
+ +
+ +
+
+
+
+ +
+
+
+ ) +} + +export default ServerTab; \ No newline at end of file diff --git a/demo/client/src/components/Settings.tsx b/demo/client/src/components/Settings.tsx new file mode 100644 index 00000000..2bd620cd --- /dev/null +++ b/demo/client/src/components/Settings.tsx @@ -0,0 +1,78 @@ +/* eslint-disable react/prop-types */ +import React, { useState } from 'react'; +import { Controlled as CodeMirror } from 'react-codemirror2'; +import 'codemirror/lib/codemirror.css'; +import 'codemirror/theme/material-darker.css'; +import 'codemirror/theme/xq-light.css'; +import 'codemirror'; +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror-graphql/lint'; +import 'codemirror-graphql/hint'; +import 'codemirror-graphql/mode'; +import beautify from 'json-beautify'; + +const Settings = ({ + graphQLRoute, + setGraphQLRoute, + + serverAddress, + setServerAddress, + redisRoute, + setRedisRoute, + schema, + clearCacheRoute, + setClearCacheRoute +} = props) => { + const [editorText, setEditorText] = useState(beautify(schema, null, 2, 80)); + + const inputArea = (_id, func, defaultVal) => { + return ( +
+ {`${_id}`} +
+ func(e.target.value)} + value={`${defaultVal}`} + /> +
+ ) + }; + + return ( + +
+
Basic Configuration
+
+ {inputArea('GraphQL Route', setGraphQLRoute, graphQLRoute)} +
+ {`${graphQLRoute === '' ? '*REQUIRED!* Please enter e' : 'E'}ndpoint where GraphQL schema will be retrieved and queries sent.`}
+ {inputArea('Server Address', setServerAddress, serverAddress)} +
+ {`${serverAddress === '' ? '*REQUIRED!* Please enter ' : ''}HTTP address of server from which Quell makes GraphQL queries.`}
+ {inputArea('Redis Route', setRedisRoute, redisRoute)} +
+ {`${redisRoute === '' ? '*REQUIRED!* Please enter e' : 'E'}ndpoint with QuellCache.getStatsFromRedis middleware installed.`}
+ {inputArea('Clear Cache Route', setClearCacheRoute, clearCacheRoute)} +
Endpoint for clearing server-side cache.
+
+
+ +
+
Retrieved GraphQL Schema
+ +
+
+ ); +}; + +export default Settings; diff --git a/demo/client/src/containers/Devtool Demo b/demo/client/src/containers/Devtool Demo new file mode 100644 index 00000000..fe384621 --- /dev/null +++ b/demo/client/src/containers/Devtool Demo @@ -0,0 +1,156 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { useState, useEffect, useRef } from 'react'; +import PrimaryNavBar from './Components/PrimaryNavBar'; +import ServerTab from './Components/ServerTab'; +import CacheTab from './Components/CacheTab'; +import ClientTab from './Components/ClientTab'; +import Logo from './assets/Quell_full_size.png'; +import isGQLQuery from './helpers/isGQLQuery'; +import { handleNavigate, handleRequestFinished } from './helpers/listeners'; + +// GraphQL +import { getIntrospectionQuery, buildClientSchema } from 'graphql'; +import Settings from './Components/Settings'; + +// Sample clientRequest data for building Network component +import data from './data/sampleClientRequests'; +import { IgnorePlugin } from 'webpack'; + +const Devtool_Demo = () => { + // queried data results + const [activeTab, setActiveTab] = useState('server'); + const [results, setResults] = useState({}); + const [schema, setSchema] = useState({}); + const [queryString, setQueryString] = useState(''); + const [graphQLRoute, setGraphQLRoute] = useState('/graphQL'); + const [clientAddress, setClientAddress] = useState( + 'http://localhost:8080' + ); + const [serverAddress, setServerAddress] = useState( + 'http://localhost:3000' + ); + const [redisRoute, setRedisRoute] = useState( + '/redis' + ); + const [clearCacheRoute, setClearCacheRoute] = useState('/clearCache'); + // changes tab - defaults to query + const [clientRequests, setClientRequests] = useState([]); + + const handleClearCache = () => { + const address=`${serverAddress}${clearCacheRoute}` + fetch(address) + .then(data => console.log(data)) + .catch(err => console.log(err)); + } + + const gqlListener = (request: chrome.devtools.network.Request): void => { + if (isGQLQuery(request)) { + request.getContent((body) => { + const responseData = JSON.parse(body); + request.responseData = responseData; + setClientRequests((prev) => prev.concat([request])); + }); + } + }; + + // COMMENT OUT IF WORKING FROM DEV SERVER + // useEffect(() => { + // handleRequestFinished(gqlListener); + // handleNavigate(gqlListener); + // }, []); + + useEffect(() => { + const introspectionQuery = getIntrospectionQuery(); + const address = `${serverAddress}${graphQLRoute}`; + fetch(address, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: introspectionQuery, + operationName: 'IntrospectionQuery', + variables: null, + }), + }) + .then((response) => response.json()) + .then((data) => { + const schema = buildClientSchema(data.data); + setSchema(schema || 'No schema retreived'); + }) + .catch((err) => console.log(err)); + }, [clientAddress, serverAddress, graphQLRoute]); + + return ( +
+ + +
+ + {activeTab === 'client' && ( + + )} + + {activeTab === 'server' && ( + <> +
+ Query Quell Server +
+ < ServerTab + clientAddress={ clientAddress } + serverAddress={ serverAddress } + graphQLRoute={ graphQLRoute } + queryString={ queryString } + setQueryString={ setQueryString } + setResults={ setResults } + schema={ schema } + clearCacheRoute={ clearCacheRoute } + results={ results } + handleClearCache={handleClearCache} + /> + + )} + + + {activeTab === 'cache' && ( +
+ +
+ )} + + {activeTab === 'settings' && ( +
+ +
+ )} +
+
+ ); +}; + +export default Devtool_Demo; diff --git a/demo/client/src/containers/Devtool.jsx b/demo/client/src/containers/Devtool.jsx new file mode 100644 index 00000000..bc38cf13 --- /dev/null +++ b/demo/client/src/containers/Devtool.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import Devtool_Demo from './Devtool Demo'; + +const Devtool = () => { + return ( + + ); +}; + +export default Devtool; \ No newline at end of file diff --git a/demo/client/src/containers/Header.jsx b/demo/client/src/containers/Header.jsx index 78503440..88af3a04 100644 --- a/demo/client/src/containers/Header.jsx +++ b/demo/client/src/containers/Header.jsx @@ -19,6 +19,9 @@ const Header = () => {
  • SERVER
  • +
  • + DEVTOOL +
  • DEMO
  • @@ -26,7 +29,7 @@ const Header = () => { TEAM
  • - + GITHUB
  • diff --git a/demo/client/src/containers/Main.jsx b/demo/client/src/containers/Main.jsx index 3de33d6c..5f767b36 100644 --- a/demo/client/src/containers/Main.jsx +++ b/demo/client/src/containers/Main.jsx @@ -5,6 +5,7 @@ import Demo from './Demo.jsx'; import Team from './Team.jsx'; import Footer from './Footer.jsx'; import Graphiql from './Graphiql.jsx'; +import Devtool from './Devtool.jsx'; const Main = () => { return ( @@ -13,6 +14,7 @@ const Main = () => { +