diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..365a10f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,38 @@ +name: CI/CD check + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +defaults: + run: + working-directory: portal + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run eslint test + run: yarn lint + + - name: Build + run: yarn build diff --git a/portal/.eslintrc.json b/portal/.eslintrc.json new file mode 100644 index 0000000..91208d0 --- /dev/null +++ b/portal/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": ["plugin:react/recommended"], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": ["react"], + "rules": { + "react/no-unescaped-entities": ["error", { "forbid": [">", "}"] }], + "prefer-destructuring": ["error", { "object": true, "array": false }] + }, + "globals": { + "window": true + } +} diff --git a/portal/components/layout.js b/portal/components/layout.js index 993c667..f941ff3 100644 --- a/portal/components/layout.js +++ b/portal/components/layout.js @@ -1,9 +1,10 @@ import Head from "next/head"; import Image from "next/image"; -import { useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import styles from "../styles/layout.module.css"; +import PropTypes from "prop-types"; -const Layout = ({ children, home }) => { +const Layout = ({ children }) => { const transitionStages = { FADE_OUT: "fadeOut", FADE_IN: "fadeIn", @@ -15,6 +16,7 @@ const Layout = ({ children, home }) => { ); const compareElem = (a, b) => { + console.log("A B ele:", a, b); return a.type.name === b.type.name; }; @@ -81,4 +83,8 @@ const Layout = ({ children, home }) => { ); }; -export default Layout; +Layout.propTypes = { + children: PropTypes.node, +}; + +export default Layout; diff --git a/portal/components/login/login.js b/portal/components/login/login.js index 2cdd9d9..888a472 100644 --- a/portal/components/login/login.js +++ b/portal/components/login/login.js @@ -1,11 +1,12 @@ -import { useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useToasts } from "react-toast-notifications"; import { getSession, signIn } from "next-auth/client"; import { useRouter } from "next/router"; import controls from "./form.config"; import styles from "../../styles/Login.module.css"; +import PropTypes from "prop-types"; -export default function Login(props) { +const Login = (props) => { const { persona } = props; const [input, setInput] = useState({}); @@ -19,7 +20,6 @@ export default function Login(props) { ); const [formValidity, setFormValidity] = useState(false); const { addToast } = useToasts(); - const [role, setRole] = useState(null); const handleInput = (e) => { setInput({ ...input, [e.target.name]: e.target.value }); @@ -53,6 +53,7 @@ export default function Login(props) { if (url) { const session = await getSession(); if (!router.isFallback) { + console.log("Role:",session.role); if (session?.role == "Admin") { router.push( persona.redirectUrl.search("http") < 0 @@ -102,4 +103,18 @@ export default function Login(props) { ); -} +}; + +Login.propTypes = { + persona: PropTypes.shape({ + consonant: PropTypes.bool, + en: PropTypes.string, + hi: PropTypes.string, + credentials: PropTypes.string, + applicationId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + redirectUrl: PropTypes.string, + redirectUrlAdmin: PropTypes.string, + }).isRequired, +}; + +export default Login; \ No newline at end of file diff --git a/portal/components/react-admin/app.js b/portal/components/react-admin/app.js index e4d0e4e..f84d95d 100644 --- a/portal/components/react-admin/app.js +++ b/portal/components/react-admin/app.js @@ -1,6 +1,6 @@ -import { useState, useEffect } from "react"; -import { AdminContext, AdminUI, Resource, useDataProvider } from "react-admin"; -import buildHasuraProvider, { buildFields } from "ra-data-hasura"; +import React, { useState, useEffect } from "react"; +import { AdminContext, AdminUI, Resource } from "react-admin"; +import buildHasuraProvider from "ra-data-hasura"; import { ApolloClient, InMemoryCache } from "@apollo/client"; import { useSession } from "next-auth/client"; import { MuiThemeProvider, createMuiTheme } from "@material-ui/core"; @@ -9,6 +9,7 @@ import customLayout from "./layout/"; import customFields from "./customHasura/customFields"; import customVariables from "./customHasura/customVariables"; import { resourceConfig } from "./layout/config"; +import PropTypes from "prop-types"; const App = () => { const [dataProvider, setDataProvider] = useState(null); @@ -25,7 +26,7 @@ const App = () => { cache: new InMemoryCache(), headers: hasuraHeaders, }); - async function buildDataProvider() { + const buildDataProvider = async () => { const hasuraProvider = await buildHasuraProvider( { client: tempClient }, { @@ -35,7 +36,7 @@ const App = () => { ); setDataProvider(() => hasuraProvider); setApolloClient(tempClient); - } + }; buildDataProvider(); }, [session]); @@ -46,7 +47,7 @@ const App = () => { ); }; -function AsyncResources({ client }) { +const AsyncResources = ({ client }) => { let introspectionResultObjects = client.cache?.data?.data?.ROOT_QUERY?.__schema.types ?.filter((obj) => obj.kind === "OBJECT") @@ -75,6 +76,10 @@ function AsyncResources({ client }) { ); -} +}; + +AsyncResources.propTypes = { + client: PropTypes.object, +}; export default App; diff --git a/portal/components/react-admin/base/components/BackButton.js b/portal/components/react-admin/base/components/BackButton.js index 7db9306..996a81d 100644 --- a/portal/components/react-admin/base/components/BackButton.js +++ b/portal/components/react-admin/base/components/BackButton.js @@ -2,6 +2,7 @@ import React from "react"; import Button from "@material-ui/core/Button"; import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos"; import { makeStyles } from "@material-ui/core"; +import PropTypes from "prop-types"; const useStyles = makeStyles((theme) => ({ button: { @@ -26,4 +27,8 @@ const BackButton = ({ history }) => { ); }; +BackButton.propTypes = { + history: PropTypes.any, +}; + export default BackButton; diff --git a/portal/components/react-admin/base/components/EditNoDeleteToolbar.js b/portal/components/react-admin/base/components/EditNoDeleteToolbar.js index 3e10921..7343f97 100644 --- a/portal/components/react-admin/base/components/EditNoDeleteToolbar.js +++ b/portal/components/react-admin/base/components/EditNoDeleteToolbar.js @@ -1,5 +1,6 @@ import React from "react"; import { Toolbar, SaveButton } from "react-admin"; +import PropTypes from "prop-types"; const EditNoDeleteToolbar = (props) => ( @@ -7,4 +8,8 @@ const EditNoDeleteToolbar = (props) => ( ); +EditNoDeleteToolbar.propTypes = { + pristine: PropTypes.bool, +}; + export default EditNoDeleteToolbar; diff --git a/portal/components/react-admin/base/resources/assessments.js b/portal/components/react-admin/base/resources/assessments.js index d9d3c1b..c4220c0 100644 --- a/portal/components/react-admin/base/resources/assessments.js +++ b/portal/components/react-admin/base/resources/assessments.js @@ -13,6 +13,7 @@ import { useMediaQuery } from "@material-ui/core"; import config from "@/components/config"; import axios from "axios"; import { useStyles } from "../styles"; +import PropTypes from "prop-types"; const DevicesFilter = (props) => { const classes = useStyles(); @@ -145,7 +146,7 @@ export const AssessmentsEdit = (props) => { `${process.env.NEXT_PUBLIC_ASSESSMENTS_MODULE_FORM_URL}/getFormPrefilled/${props.id}` ) .then((res) => setFormUrl(res)) - .catch((err) => {}); + .catch(() => {}); }, []); return ( <> @@ -170,3 +171,13 @@ export const AssessmentsEdit = (props) => { > ); }; + +AssessmentsCreate.propTypes = { + options: PropTypes.object, + id: PropTypes.number, +}; + +AssessmentsEdit.propTypes = { + options: PropTypes.object, + id: PropTypes.number, +}; diff --git a/portal/components/react-admin/base/resources/gradeAssessment.js b/portal/components/react-admin/base/resources/gradeAssessment.js index 0b43f70..b744aa3 100644 --- a/portal/components/react-admin/base/resources/gradeAssessment.js +++ b/portal/components/react-admin/base/resources/gradeAssessment.js @@ -8,18 +8,16 @@ import { DateField, SelectInput, SearchInput, - ReferenceInput, - AutocompleteInput, - DateInput, Filter, + TextInput, } from "react-admin"; import { useMediaQuery } from "@material-ui/core"; import { useStyles } from "../styles"; // school renderer -const schoolRenderer = (choice) => { - return choice ? `UDISE: ${choice.udise}, ${choice.name}` : ""; -}; +// const schoolRenderer = (choice) => { +// return choice ? `UDISE: ${choice.udise}, ${choice.name}` : ""; +// }; const SearchFilter = (props) => ( diff --git a/portal/components/react-admin/base/resources/schools.js b/portal/components/react-admin/base/resources/schools.js index 57e46a9..5fd88bb 100644 --- a/portal/components/react-admin/base/resources/schools.js +++ b/portal/components/react-admin/base/resources/schools.js @@ -3,31 +3,23 @@ import { List, SimpleList, Datagrid, - DateField, TextField, - BooleanField, FunctionField, Edit, SimpleForm, - TextInput, - SelectInput, - Filter, - SearchInput, useRedirect, useNotify, - FormDataConsumer, - AutocompleteInput, - ReferenceInput, } from "react-admin"; import { useSession } from "next-auth/client"; -import { Typography, useMediaQuery } from "@material-ui/core"; +import { useMediaQuery } from "@material-ui/core"; import EditNoDeleteToolbar from "../components/EditNoDeleteToolbar"; import BackButton from "../components/BackButton"; import config from "@/components/config"; import sendSMS from "@/utils/sendSMS"; import buildGupshup from "@/utils/buildGupshup"; import { useStyles } from "../styles"; +import PropTypes from "prop-types"; /** * Donate Device Request List @@ -158,3 +150,10 @@ export const SchoolEdit = (props) => { ); }; + +SchoolEdit.propTypes = { + mutationMode: PropTypes.string, + basePath: PropTypes.string, + record: PropTypes.object, + history: PropTypes.any, +}; diff --git a/portal/components/react-admin/base/resources/teachers.js b/portal/components/react-admin/base/resources/teachers.js index f9567b2..be720d7 100644 --- a/portal/components/react-admin/base/resources/teachers.js +++ b/portal/components/react-admin/base/resources/teachers.js @@ -3,40 +3,30 @@ import { List, SimpleList, Datagrid, - DateField, TextField, - BooleanField, - FunctionField, Edit, SimpleForm, - TextInput, SelectInput, Filter, - SearchInput, useRedirect, useNotify, - FormDataConsumer, - AutocompleteInput, - ReferenceInput, - ChipField, } from "react-admin"; import { useSession } from "next-auth/client"; -import { Typography, useMediaQuery, Chip } from "@material-ui/core"; +import { useMediaQuery, Chip } from "@material-ui/core"; import EditNoDeleteToolbar from "../components/EditNoDeleteToolbar"; import BackButton from "../components/BackButton"; import config from "@/components/config"; import sendSMS from "@/utils/sendSMS"; -import buildGupshup from "@/utils/buildGupshup"; import { useStyles } from "../styles"; +import PropTypes from "prop-types"; -const getChoice = (choices, id) => { - return choices?.find((elem) => elem.id === id); -}; +// const getChoice = (choices, id) => { +// return choices?.find((elem) => elem.id === id); +// }; const DevicesFilter = (props) => { const classes = useStyles(); - const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); return ( { ); }; +TeacherList.propTypes = { + record: PropTypes.array, + source: PropTypes.string, +}; + export const TeacherEdit = (props) => { const classes = useStyles(); const notify = useNotify(); @@ -217,3 +212,10 @@ export const TeacherEdit = (props) => { ); }; + +TeacherEdit.propTypes = { + mutationMode: PropTypes.string, + basePath: PropTypes.string, + record: PropTypes.object, + history: PropTypes.any, +}; diff --git a/portal/components/react-admin/customHasura/customVariables.js b/portal/components/react-admin/customHasura/customVariables.js index 6541208..ea676d6 100644 --- a/portal/components/react-admin/customHasura/customVariables.js +++ b/portal/components/react-admin/customHasura/customVariables.js @@ -16,38 +16,37 @@ const SPLIT_TOKEN = "#"; import getFinalType from "./getFinalType"; -const buildGetListVariables = - (introspectionResults) => (resource, aorFetchType, params) => { - const result = {}; - let { filter: filterObj = {} } = params; - const { customFilters = [] } = params; +const buildGetListVariables = () => (resource, aorFetchType, params) => { + const result = {}; + let { filter: filterObj = {} } = params; + const { customFilters = [] } = params; - const distinctOnField = "distinct_on"; - /** Setting "distinct_on" to be the `filters` object attribute to be used inside RA - * and setting to a `distinct_on` variable - * and removing from the filter object - */ - const { distinct_on = "" } = filterObj; - filterObj = omit(filterObj, [distinctOnField]); + const distinctOnField = "distinct_on"; + /** Setting "distinct_on" to be the `filters` object attribute to be used inside RA + * and setting to a `distinct_on` variable + * and removing from the filter object + */ + const { distinct_on = "" } = filterObj; + filterObj = omit(filterObj, [distinctOnField]); - /** - * Nested entities are parsed by CRA, which returns a nested object - * { 'level1': {'level2': 'test'}} - * instead of { 'level1.level2': 'test'} - * That's why we use a HASH for properties, when we declared nested stuff at CRA: - * level1#level2@_ilike - */ + /** + * Nested entities are parsed by CRA, which returns a nested object + * { 'level1': {'level2': 'test'}} + * instead of { 'level1.level2': 'test'} + * That's why we use a HASH for properties, when we declared nested stuff at CRA: + * level1#level2@_ilike + */ - /** + /** keys with comma separated values { 'title@ilike,body@like,authors@similar': 'test', 'col1@like,col2@like': 'val' } */ - const orFilterKeys = Object.keys(filterObj).filter((e) => e.includes(",")); + const orFilterKeys = Object.keys(filterObj).filter((e) => e.includes(",")); - /** + /** format filters { 'title@ilike': 'test', @@ -57,110 +56,110 @@ const buildGetListVariables = 'col2@like': 'val' } */ - const orFilterObj = orFilterKeys.reduce((acc, commaSeparatedKey) => { - const keys = commaSeparatedKey.split(","); - return { - ...acc, - ...keys.reduce((acc2, key) => { - return { - ...acc2, - [key]: filterObj[commaSeparatedKey], - }; - }, {}), - }; - }, {}); - filterObj = omit(filterObj, orFilterKeys); - - const makeNestedFilter = (obj, operation) => { - if (Object.keys(obj).length === 1) { - const [key] = Object.keys(obj); - return { [key]: makeNestedFilter(obj[key], operation) }; - } else { - return { [operation]: obj }; - } + const orFilterObj = orFilterKeys.reduce((acc, commaSeparatedKey) => { + const keys = commaSeparatedKey.split(","); + return { + ...acc, + ...keys.reduce((acc2, key) => { + return { + ...acc2, + [key]: filterObj[commaSeparatedKey], + }; + }, {}), }; + }, {}); + filterObj = omit(filterObj, orFilterKeys); - const filterReducer = (obj) => (acc, key) => { - let filter; - if (key === "ids") { - filter = { id: { _in: obj["ids"] } }; - } else if (Array.isArray(obj[key])) { - filter = { [key]: { _in: obj[key] } }; - } else if (obj[key] && obj[key].format === "hasura-raw-query") { - filter = { [key]: obj[key].value || {} }; - } else { - let [keyName, operation = ""] = key.split("@"); - let operator; - const field = resource.type.fields.find((f) => f.name === keyName); - if (field) { - switch (getFinalType(field.type).name) { - case "String": - operation = operation || "_ilike"; - operator = { - [operation]: operation.includes("like") - ? `%${obj[key]}%` - : obj[key], - }; - filter = set({}, keyName.split(SPLIT_TOKEN), operator); - break; - default: - operator = { - [operation]: operation.includes("like") - ? `%${obj[key]}%` - : obj[key], - }; - filter = set({}, keyName.split(SPLIT_TOKEN), { - [operation || "_eq"]: obj[key], - }); - } - } else { - // Else block runs when the field is not found in Graphql schema. - // Most likely it's nested. If it's not, it's better to let - // Hasura fail with a message than silently fail/ignore it - operator = { - [operation || "_eq"]: operation.includes("like") - ? `%${obj[key]}%` - : obj[key], - }; - filter = set({}, keyName.split(SPLIT_TOKEN), operator); + // const makeNestedFilter = (obj, operation) => { + // if (Object.keys(obj).length === 1) { + // const [key] = Object.keys(obj); + // return { [key]: makeNestedFilter(obj[key], operation) }; + // } else { + // return { [operation]: obj }; + // } + // }; + + const filterReducer = (obj) => (acc, key) => { + let filter; + if (key === "ids") { + filter = { id: { _in: obj["ids"] } }; + } else if (Array.isArray(obj[key])) { + filter = { [key]: { _in: obj[key] } }; + } else if (obj[key] && obj[key].format === "hasura-raw-query") { + filter = { [key]: obj[key].value || {} }; + } else { + let [keyName, operation = ""] = key.split("@"); + let operator; + const field = resource.type.fields.find((f) => f.name === keyName); + if (field) { + switch (getFinalType(field.type).name) { + case "String": + operation = operation || "_ilike"; + operator = { + [operation]: operation.includes("like") + ? `%${obj[key]}%` + : obj[key], + }; + filter = set({}, keyName.split(SPLIT_TOKEN), operator); + break; + default: + operator = { + [operation]: operation.includes("like") + ? `%${obj[key]}%` + : obj[key], + }; + filter = set({}, keyName.split(SPLIT_TOKEN), { + [operation || "_eq"]: obj[key], + }); } + } else { + // Else block runs when the field is not found in Graphql schema. + // Most likely it's nested. If it's not, it's better to let + // Hasura fail with a message than silently fail/ignore it + operator = { + [operation || "_eq"]: operation.includes("like") + ? `%${obj[key]}%` + : obj[key], + }; + filter = set({}, keyName.split(SPLIT_TOKEN), operator); } - return [...acc, filter]; - }; - const andFilters = Object.keys(filterObj) - .reduce(filterReducer(filterObj), customFilters) - .filter(Boolean); - const orFilters = Object.keys(orFilterObj) - .reduce(filterReducer(orFilterObj), []) - .filter(Boolean); + } + return [...acc, filter]; + }; + const andFilters = Object.keys(filterObj) + .reduce(filterReducer(filterObj), customFilters) + .filter(Boolean); + const orFilters = Object.keys(orFilterObj) + .reduce(filterReducer(orFilterObj), []) + .filter(Boolean); - result["where"] = { - _and: andFilters, - ...(orFilters.length && { _or: orFilters }), - }; + result["where"] = { + _and: andFilters, + ...(orFilters.length && { _or: orFilters }), + }; - if (params.pagination) { - result["limit"] = parseInt(params.pagination.perPage, 10); - result["offset"] = parseInt( - (params.pagination.page - 1) * params.pagination.perPage, - 10 - ); - } + if (params.pagination) { + result["limit"] = parseInt(params.pagination.perPage, 10); + result["offset"] = parseInt( + (params.pagination.page - 1) * params.pagination.perPage, + 10 + ); + } - if (params.sort) { - result["order_by"] = set( - {}, - params.sort.field, - params.sort.order.toLowerCase() - ); - } + if (params.sort) { + result["order_by"] = set( + {}, + params.sort.field, + params.sort.order.toLowerCase() + ); + } - if (distinct_on) { - result["distinct_on"] = distinct_on; - } + if (distinct_on) { + result["distinct_on"] = distinct_on; + } - return result; - }; + return result; +}; /** * Returns a reducer that converts the react-admin key-values to hasura-acceptable values @@ -191,7 +190,7 @@ const typeAwareKeyValueReducer = }; const buildUpdateVariables = - (introspectionResults) => (resource, aorFetchType, params, queryType) => { + (introspectionResults) => (resource, aorFetchType, params) => { const reducer = typeAwareKeyValueReducer( introspectionResults, resource, @@ -231,7 +230,7 @@ const buildUpdateVariables = }; const buildCreateVariables = - (introspectionResults) => (resource, aorFetchType, params, queryType) => { + (introspectionResults) => (resource, aorFetchType, params) => { const reducer = typeAwareKeyValueReducer( introspectionResults, resource, diff --git a/portal/components/react-admin/layout/customAppBar.js b/portal/components/react-admin/layout/customAppBar.js index 58e55ca..b00c7bc 100644 --- a/portal/components/react-admin/layout/customAppBar.js +++ b/portal/components/react-admin/layout/customAppBar.js @@ -38,7 +38,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -const AppBarCustom = (props) => { +const AppBarCustom = () => { const classes = useStyles(); const dispatch = useDispatch(); const open = useSelector((state) => state.admin.ui.sidebarOpen); diff --git a/portal/components/react-admin/layout/customBreadcrumbs.js b/portal/components/react-admin/layout/customBreadcrumbs.js index 1d8443b..817cc2c 100644 --- a/portal/components/react-admin/layout/customBreadcrumbs.js +++ b/portal/components/react-admin/layout/customBreadcrumbs.js @@ -1,39 +1,39 @@ -import React from "react"; -import config from "./config"; +// import React from "react"; +// import config from "./config"; -const useStyles = makeStyles((theme) => ({ - logOutButton: { - color: theme.palette.grey[500], - padding: "1rem", - fontSize: "0.8rem", - textTransform: "capitalize", - }, - logOutIcon: { - fontSize: "1rem", - marginRight: "0.5rem", - transform: "translateY(-10%)", - }, -})); +// const useStyles = makeStyles((theme) => ({ +// logOutButton: { +// color: theme.palette.grey[500], +// padding: "1rem", +// fontSize: "0.8rem", +// textTransform: "capitalize", +// }, +// logOutIcon: { +// fontSize: "1rem", +// marginRight: "0.5rem", +// transform: "translateY(-10%)", +// }, +// })); -const Breadcrumbs = ({ location, source, record }) => { - const { resource = null, id = null } = location ?? {}; - const labels = []; - labels.push(getResourceName(resource)); - if (!id) labels.push("List"); - else labels.push(record[source]); +// const Breadcrumbs = ({ location, source, record }) => { +// const { resource = null, id = null } = location ?? {}; +// const labels = []; +// labels.push(getResourceName(resource)); +// if (!id) labels.push("List"); +// else labels.push(record[source]); - return ( - - {labels.map((label, index) => { - return ( - <> - {label} - {index === labels.length ? / : null} - > - ); - })} - - ); -}; +// return ( +// +// {labels.map((label, index) => { +// return ( +// <> +// {label} +// {index === labels.length ? / : null} +// > +// ); +// })} +// +// ); +// }; -export default Logout; +// export default Logout; diff --git a/portal/components/react-admin/layout/customSidebar.js b/portal/components/react-admin/layout/customSidebar.js index 090aa1a..05d0774 100644 --- a/portal/components/react-admin/layout/customSidebar.js +++ b/portal/components/react-admin/layout/customSidebar.js @@ -2,11 +2,11 @@ import React, { useState, useEffect } from "react"; import { withRouter } from "react-router-dom"; import { makeStyles } from "@material-ui/core/styles"; import { ListSubheader } from "@material-ui/core"; -import clsx from "clsx"; import UserSidebarHeader from "./sidebarHeader"; import VerticalCollapse from "./verticalCollapse"; import VerticalItem from "./verticalItem"; import { resourceConfig } from "./config"; +import PropTypes from "prop-types"; const useStyles = makeStyles((theme) => ({ listTitle: { @@ -99,4 +99,15 @@ const SidebarWrapper = React.memo(function SidebarWrapper({ ); }); +CustomSidebar.propTypes = { + location: PropTypes.string, + activePath: PropTypes.string, + resources: PropTypes.arrayOf(PropTypes.object), +}; + +SidebarWrapper.propTypes = { + activePath: PropTypes.string, + filteredResources: PropTypes.arrayOf(PropTypes.object), +}; + export default withRouter((props) => ); diff --git a/portal/components/react-admin/layout/customUserMenu.js b/portal/components/react-admin/layout/customUserMenu.js index 9e2ca39..055290b 100644 --- a/portal/components/react-admin/layout/customUserMenu.js +++ b/portal/components/react-admin/layout/customUserMenu.js @@ -6,9 +6,10 @@ import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; import Popover from "@material-ui/core/Popover"; import { useSession } from "next-auth/client"; import CustomLogoutButton from "./logoutButton"; +import PropTypes from "prop-types"; const UserMenu = ({ logout }) => { - const [session, loading] = useSession(); + const [session] = useSession(); if (session) { return ; } @@ -29,7 +30,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -const UserMenuComponent = ({ user, logout }) => { +const UserMenuComponent = ({ user }) => { const [userMenu, setUserMenu] = React.useState(null); const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); const classes = useStyles(); @@ -82,4 +83,12 @@ const UserMenuComponent = ({ user, logout }) => { ); }; +UserMenu.propTypes = { + logout: PropTypes.any, +}; + +UserMenuComponent.propTypes = { + user: PropTypes.object, +}; + export default UserMenu; diff --git a/portal/components/react-admin/layout/index.js b/portal/components/react-admin/layout/index.js index e9265c2..181066c 100644 --- a/portal/components/react-admin/layout/index.js +++ b/portal/components/react-admin/layout/index.js @@ -6,9 +6,10 @@ import { setSidebarVisibility, Sidebar, } from "react-admin"; -import { makeStyles, useMediaQuery } from "@material-ui/core"; +import { makeStyles } from "@material-ui/core"; import CustomSidebar from "./customSidebar"; import AppBar from "./customAppBar"; +import PropTypes from "prop-types"; const useStyles = makeStyles((theme) => ({ wrapper: { @@ -81,9 +82,19 @@ const CustomLayout = (props) => { ); }; +CustomLayout.propTypes = { + children: PropTypes.element, + logout: PropTypes.any, + open: PropTypes.bool, + title: PropTypes.string, + sidebarOpen: PropTypes.bool, + resources: PropTypes.arrayOf(PropTypes.object), +}; + const mapStateToProps = (state) => ({ isLoading: state.admin.loading > 0, resources: getResources(state), sidebarOpen: state.admin.ui.sidebarOpen, }); + export default connect(mapStateToProps, { setSidebarVisibility })(CustomLayout); diff --git a/portal/components/react-admin/layout/sidebarHeader.js b/portal/components/react-admin/layout/sidebarHeader.js index dfa4039..d1f5f53 100644 --- a/portal/components/react-admin/layout/sidebarHeader.js +++ b/portal/components/react-admin/layout/sidebarHeader.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import Image from "next/image"; import { makeStyles } from "@material-ui/core/styles"; diff --git a/portal/components/react-admin/layout/verticalCollapse.js b/portal/components/react-admin/layout/verticalCollapse.js index fc6e55e..5d1e12b 100644 --- a/portal/components/react-admin/layout/verticalCollapse.js +++ b/portal/components/react-admin/layout/verticalCollapse.js @@ -10,6 +10,7 @@ import clsx from "clsx"; import VerticalItem from "./verticalItem"; import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp"; +import PropTypes from "prop-types"; const useStyles = makeStyles((theme) => ({ root: { @@ -65,7 +66,7 @@ const needsToBeOpened = (location, item) => { return location && isUrlInChildren(item, location.pathname); }; -function VerticalCollapse({ activePath, ...props }) { +const VerticalCollapse = ({ activePath, ...props }) => { const [open, setOpen] = useState(() => needsToBeOpened(window.location, props.item) ); @@ -81,9 +82,9 @@ function VerticalCollapse({ activePath, ...props }) { } }, [item]); - function handleClick() { + const handleClick = () => { setOpen(!open); - } + }; return ( @@ -129,7 +130,6 @@ function VerticalCollapse({ activePath, ...props }) { item={i} publicity={publicity} nestedLevel={props.nestedLevel + 1} - permissions={permissions} /> ); } @@ -146,6 +146,13 @@ function VerticalCollapse({ activePath, ...props }) { )} ); -} +}; + +VerticalCollapse.propTypes = { + item: PropTypes.object, + nestedLevel: PropTypes.number, + activePath: PropTypes.string, + publicity: PropTypes.any, +}; export default VerticalCollapse; diff --git a/portal/components/react-admin/layout/verticalItem.js b/portal/components/react-admin/layout/verticalItem.js index dd32a0e..b25ba95 100644 --- a/portal/components/react-admin/layout/verticalItem.js +++ b/portal/components/react-admin/layout/verticalItem.js @@ -1,10 +1,11 @@ -import React, { createElement } from "react"; +import React from "react"; import { makeStyles } from "@material-ui/core/styles"; import { Link } from "react-router-dom"; import SmartphoneIcon from "@material-ui/icons/Smartphone"; import SchoolIcon from "@material-ui/icons/School"; import PersonIcon from "@material-ui/icons/Person"; import PieChartIcon from "@material-ui/icons/PieChart"; +import PropTypes from "prop-types"; const useStyles = makeStyles((theme) => ({ sidebarItem: { @@ -51,7 +52,6 @@ const VerticalItem = (props) => { const classes = useStyles({ itemPadding: nestedLevel > 0 ? 30 + nestedLevel * 16 : 24, }); - const { onMenuClick } = props; let sidebarItemName = item.label; if (item.options !== undefined && item.options.label !== undefined) { @@ -70,4 +70,15 @@ const VerticalItem = (props) => { ); }; +Icon.propTypes = { + type: PropTypes.string, + className: PropTypes.object, +}; + +VerticalItem.propTypes = { + item: PropTypes.object, + nestedLevel: PropTypes.number, + activePath: PropTypes.string, +}; + export default VerticalItem; diff --git a/portal/components/track/track.js b/portal/components/track/track.js index 9b3f234..7b8039d 100644 --- a/portal/components/track/track.js +++ b/portal/components/track/track.js @@ -1,5 +1,5 @@ import Image from "next/image"; -import { useState, useRef, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import styles from "../../styles/Track.module.css"; import controls from "./track.config"; import axios from "axios"; @@ -16,7 +16,6 @@ const Track = () => { const [trackingResponse, setTrackingResponse] = useState(null); const [deliveryStatus, setDeliveryStatus] = useState(false); const [displayCertificate, setDisplayCertificate] = useState(false); - const captchaRef = useRef(null); useEffect(() => { const obj = config.statusChoices.find( @@ -45,7 +44,7 @@ const Track = () => { const { addToast } = useToasts(); useEffect(() => { - const response = axios + axios .get(process.env.NEXT_PUBLIC_CAPTCHA_URL) .then((resp) => { const { blob } = resp.data; diff --git a/portal/package.json b/portal/package.json index 3757e07..c677372 100644 --- a/portal/package.json +++ b/portal/package.json @@ -25,11 +25,19 @@ "react": "17.0.2", "react-admin": "^3.16.2", "react-dom": "17.0.2", - "react-toast-notifications": "^2.4.4" + "react-toast-notifications": "^2.4.4", + "@fusionauth/node-client": "^1.34.0" }, "devDependencies": { "eslint": "7.29.0", + "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "11.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.5.0", "prettier": "^2.6.2" } } diff --git a/portal/pages/_app.js b/portal/pages/_app.js index 85fc04a..9e17a06 100644 --- a/portal/pages/_app.js +++ b/portal/pages/_app.js @@ -1,6 +1,8 @@ import "../styles/globals.css"; +import React from "react"; import { Provider } from "next-auth/client"; import { ToastProvider } from "react-toast-notifications"; +import PropTypes from "prop-types"; function MyApp({ Component, pageProps }) { return ( @@ -16,4 +18,9 @@ function MyApp({ Component, pageProps }) { ); } +MyApp.propTypes = { + Component: PropTypes.element, + pageProps: PropTypes.node, +}; + export default MyApp; diff --git a/portal/pages/_document.js b/portal/pages/_document.js index fa84d98..715efac 100644 --- a/portal/pages/_document.js +++ b/portal/pages/_document.js @@ -1,4 +1,5 @@ import Document, { Html, Head, Main, NextScript } from "next/document"; +import React from "react"; class MyDocument extends Document { static async getInitialProps(ctx) { diff --git a/portal/pages/admin.js b/portal/pages/admin.js index c685383..e470838 100644 --- a/portal/pages/admin.js +++ b/portal/pages/admin.js @@ -1,4 +1,5 @@ import dynamic from "next/dynamic"; +import React from "react"; import { useSession } from "next-auth/client"; import Login from "./login"; diff --git a/portal/pages/api/auth/[...nextauth].js b/portal/pages/api/auth/[...nextauth].js index 96ab9ef..eb14570 100644 --- a/portal/pages/api/auth/[...nextauth].js +++ b/portal/pages/api/auth/[...nextauth].js @@ -24,7 +24,7 @@ export default NextAuth({ Providers.Credentials({ id: "fusionauth", name: "FusionAuth Credentials Login", - async authorize(credentials, req) { + async authorize(credentials) { let response = null; try { response = await fusionAuthLogin( @@ -46,10 +46,10 @@ export default NextAuth({ jwt: true, }, callbacks: { - redirect(url, baseUrl) { + redirect(url) { return url; }, - async jwt(token, user, account, profile, isNewUser) { + async jwt(token, user, account, profile) { // Add access_token to the token right after signin const registrationElement = profile?.user?.registrations?.filter( (element) => diff --git a/portal/pages/api/captcha.js b/portal/pages/api/captcha.js index 8db0df4..014b995 100644 --- a/portal/pages/api/captcha.js +++ b/portal/pages/api/captcha.js @@ -1,5 +1,4 @@ import axios from "axios"; -import { getSession, session } from "next-auth/client"; const handler = async (req, res) => { if (req.method === "POST") { @@ -24,13 +23,11 @@ const handler = async (req, res) => { } return true; } catch (err) { - res - .status(500) - .json({ - errors: "Captcha service unavailable", - success: null, - err: err, - }); + res.status(500).json({ + errors: "Captcha service unavailable", + success: null, + err: err, + }); return true; } } diff --git a/portal/pages/api/certificate.js b/portal/pages/api/certificate.js index d21d84c..c325f59 100644 --- a/portal/pages/api/certificate.js +++ b/portal/pages/api/certificate.js @@ -1,5 +1,4 @@ import axios from "axios"; -import { getSession, session } from "next-auth/client"; const handler = async (req, res) => { if (req.method === "POST") { diff --git a/portal/pages/api/graphql.js b/portal/pages/api/graphql.js index 52b5c65..30c29a8 100644 --- a/portal/pages/api/graphql.js +++ b/portal/pages/api/graphql.js @@ -1,5 +1,5 @@ import axios from "axios"; -import { getSession, session } from "next-auth/client"; +import { getSession } from "next-auth/client"; const handler = async (req, res) => { const session = await getSession({ req }); diff --git a/portal/pages/api/log.js b/portal/pages/api/log.js index 9f0e663..99fe96f 100644 --- a/portal/pages/api/log.js +++ b/portal/pages/api/log.js @@ -1,5 +1,5 @@ import axios from "axios"; -import { getSession, session } from "next-auth/client"; +import { getSession } from "next-auth/client"; const handler = async (req, res) => { const session = await getSession({ req }); diff --git a/portal/pages/api/sms.js b/portal/pages/api/sms.js index f9f28ac..9a00ba5 100644 --- a/portal/pages/api/sms.js +++ b/portal/pages/api/sms.js @@ -1,5 +1,5 @@ import axios from "axios"; -import { getSession, session } from "next-auth/client"; +import { getSession } from "next-auth/client"; const handler = async (req, res) => { const session = await getSession({ req }); diff --git a/portal/pages/api/track.js b/portal/pages/api/track.js index e197073..aecf045 100644 --- a/portal/pages/api/track.js +++ b/portal/pages/api/track.js @@ -4,7 +4,7 @@ const handler = async (req, res) => { if (req.method === "POST") { try { const { captcha, captchaToken } = req.body; - const responseObjectCaptcha = await captchaVerify(captcha, captchaToken); + await captchaVerify(captcha, captchaToken); const { id } = req.body; const responseObject = await startFetchTrackDevice(id); if (responseObject?.errors) { @@ -36,15 +36,15 @@ const handler = async (req, res) => { } }; -function maskPhoneNumber(array) { +const maskPhoneNumber = (array) => { const obj = array[0]; let { phone_number } = obj; phone_number = `******${phone_number.slice(6)}`; obj.phone_number = phone_number; return obj; -} +}; -async function captchaVerify(captcha, captchaToken) { +const captchaVerify = async (captcha, captchaToken) => { const result = await axios({ method: "POST", url: `${process.env.NEXT_PUBLIC_CAPTCHA_URL}`, @@ -55,9 +55,9 @@ async function captchaVerify(captcha, captchaToken) { }); return result; -} +}; -async function fetchGraphQL(operationsDoc, operationName, variables) { +const fetchGraphQL = async (operationsDoc, operationName, variables) => { const result = await axios({ method: "POST", headers: { @@ -72,7 +72,7 @@ async function fetchGraphQL(operationsDoc, operationName, variables) { }); return await result; -} +}; const operationsDoc = ` query trackDevice($trackingKey: String) { @@ -87,16 +87,16 @@ const operationsDoc = ` } `; -function fetchTrackDevice(trackingKey) { +const fetchTrackDevice = (trackingKey) => { return fetchGraphQL(operationsDoc, "trackDevice", { trackingKey: trackingKey, }); -} +}; -async function startFetchTrackDevice(trackingKey) { +const startFetchTrackDevice = async (trackingKey) => { const response = await fetchTrackDevice(trackingKey); return response.data; -} +}; export default handler; diff --git a/portal/pages/index.js b/portal/pages/index.js index 77e4b0d..f709073 100644 --- a/portal/pages/index.js +++ b/portal/pages/index.js @@ -1,38 +1,34 @@ -import Image from "next/image"; -import Link from "next/link"; -import Layout from "../components/layout"; -import styles from "../styles/Home.module.css"; -import config from "@/components/config"; +import React from "react"; import Login from "./login"; const Home = () => { return ; - return ( - - - {config.homepageCards.map((card, index) => { - return ( - - - - {card.icon} - - - {" "} - {card.title.en} / - {card.title.hi} ⟶ - - - - ); - })} - - - ); + // return ( + // + // + // {config.homepageCards.map((card, index) => { + // return ( + // + // + // + // {card.icon} + // + // + // {" "} + // {card.title.en} / + // {card.title.hi} ⟶ + // + // + // + // ); + // })} + // + // + // ); }; export default Home; diff --git a/portal/pages/login.js b/portal/pages/login.js index 73926d4..b7f0a51 100644 --- a/portal/pages/login.js +++ b/portal/pages/login.js @@ -1,5 +1,4 @@ -import Image from "next/image"; -import { useState } from "react"; +import React, { useState } from "react"; import Layout from "../components/layout"; import Login from "../components/login/login"; import styles from "../styles/Login.module.css"; diff --git a/portal/pages/school.js b/portal/pages/school.js index e0bbed5..3fde798 100644 --- a/portal/pages/school.js +++ b/portal/pages/school.js @@ -1,4 +1,5 @@ import Link from "next/link"; +import React from "react"; import { useSession } from "next-auth/client"; import Layout from "../components/layout"; import Login from "./login"; diff --git a/portal/pages/track.js b/portal/pages/track.js index 2d19ac5..f8b3922 100644 --- a/portal/pages/track.js +++ b/portal/pages/track.js @@ -1,9 +1,8 @@ -import Image from "next/image"; -import { useState, useEffect } from "react"; +import React from "react"; import Layout from "../components/layout"; import Track from "../components/track/track"; -const TrackWrapper = (props) => { +const TrackWrapper = () => { return ( diff --git a/portal/yarn.lock b/portal/yarn.lock index 54cd6b9..d53c092 100644 --- a/portal/yarn.lock +++ b/portal/yarn.lock @@ -1115,6 +1115,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +confusing-browser-globals@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + connected-react-router@^6.5.2: version "6.9.2" resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.9.2.tgz#f89fa87f0e977fcabf17475fb4552e170cc7e48e" @@ -1541,6 +1546,25 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + +eslint-config-airbnb@^19.0.4: + version "19.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== + dependencies: + eslint-config-airbnb-base "^15.0.0" + object.assign "^4.1.2" + object.entries "^1.1.5" + eslint-config-next@11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-11.0.0.tgz#0638a839dd46bbf5391076b13c48b6c0cc92ec2f" @@ -1555,6 +1579,11 @@ eslint-config-next@11.0.0: eslint-plugin-react "^7.23.1" eslint-plugin-react-hooks "^4.2.0" +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" @@ -1571,7 +1600,7 @@ eslint-module-utils@^2.7.3: debug "^3.2.7" find-up "^2.1.0" -eslint-plugin-import@^2.22.1: +eslint-plugin-import@^2.22.1, eslint-plugin-import@^2.26.0: version "2.26.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== @@ -1590,7 +1619,7 @@ eslint-plugin-import@^2.22.1: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-jsx-a11y@^6.4.1: +eslint-plugin-jsx-a11y@^6.4.1, eslint-plugin-jsx-a11y@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== @@ -1608,12 +1637,19 @@ eslint-plugin-jsx-a11y@^6.4.1: language-tags "^1.0.5" minimatch "^3.0.4" -eslint-plugin-react-hooks@^4.2.0: +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react-hooks@^4.2.0, eslint-plugin-react-hooks@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz#5f762dfedf8b2cf431c689f533c9d3fa5dcf25ad" integrity sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw== -eslint-plugin-react@^7.23.1: +eslint-plugin-react@^7.23.1, eslint-plugin-react@^7.29.4: version "7.29.4" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== @@ -1774,6 +1810,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" @@ -3288,6 +3329,13 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + prettier@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"