From 048dc1d1d944b5feace5a418e2cd67f93ce9c555 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Mon, 18 Mar 2024 15:14:44 +0530
Subject: [PATCH 1/7] feat: better eslint rules
---
.eslintignore | 15 ++++++-
.eslintrc.js | 14 ++++++-
app/utils/index.js | 3 +-
app/utils/injectSaga.js | 52 +++++++++++-------------
package.json | 1 +
pages/_app.js | 38 ++++++++----------
pages/_document.js | 88 +++++++++++++++++++++--------------------
yarn.lock | 4 ++
8 files changed, 117 insertions(+), 98 deletions(-)
diff --git a/.eslintignore b/.eslintignore
index 24c24a1..9053902 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -9,4 +9,17 @@ npm-debug.log
.idea
**/coverage/**
**/storybook-static/**
-**/server/**
\ No newline at end of file
+**/server/**
+lighthouserc.js
+lingui.config.js
+__tests__
+internals/**/*.*
+coverage/**/*.*
+reports/**/*.*
+badges/**/*.*
+assets/**/*.*
+**/tests/**/*.test.js
+playwright.config.js
+babel.config.js
+app/translations/*.js
+app/**/stories/**/*.*
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
index 0c340bf..e3e0106 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -9,15 +9,25 @@ module.exports = {
es2021: true,
'jest/globals': true
},
+ plugins: ['jest', 'immutable', 'prettier'],
extends: ['eslint:recommended', 'prettier', 'plugin:import/recommended', 'next'],
- plugins: ['prettier', 'jest'],
rules: {
'prettier/prettier': ['error', prettierOptions],
'import/no-webpack-loader-syntax': 0,
'react/display-name': 0,
'react/react-in-jsx-scope': 'off',
curly: ['error', 'all'],
- 'no-console': ['error', { allow: ['error'] }]
+ 'no-console': ['error', { allow: ['error'] }],
+ 'max-lines': ['error', { max: 300, skipBlankLines: true, skipComments: true }],
+ 'max-lines-per-function': ['error', 250],
+ 'no-else-return': 'error',
+ 'max-params': ['error', 3],
+ 'no-shadow': 'error',
+ complexity: ['error', 5],
+ 'no-empty': 'error',
+ 'import/order': ['error', { groups: [['builtin', 'external', 'internal', 'parent', 'sibling', 'index']] }],
+ 'immutable/no-this': 2,
+ 'eslint-comments/no-use': 0
},
globals: {
GLOBAL: false,
diff --git a/app/utils/index.js b/app/utils/index.js
index ca6520b..852c625 100644
--- a/app/utils/index.js
+++ b/app/utils/index.js
@@ -36,9 +36,8 @@ export const setDeviceType = (width = document.body.clientWidth) => {
return 'mobile';
} else if (width >= screenSizes.tablet && width < screenSizes.desktop) {
return 'tablet';
- } else {
- return 'desktop';
}
+ return 'desktop';
};
export const getDeviceType = (device) => (device || setDeviceType()).toUpperCase();
diff --git a/app/utils/injectSaga.js b/app/utils/injectSaga.js
index ba34014..cb5eecd 100644
--- a/app/utils/injectSaga.js
+++ b/app/utils/injectSaga.js
@@ -1,7 +1,6 @@
-import React from 'react';
+import React, { useContext, useEffect } from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { ReactReduxContext } from 'react-redux';
-
import getInjectors from './sagaInjectors';
/**
@@ -16,39 +15,34 @@ import getInjectors from './sagaInjectors';
* - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again.
*
*/
-export default ({ key, saga, mode }) =>
+const withSaga =
+ ({ key, saga, mode }) =>
(WrappedComponent) => {
- class InjectSaga extends React.Component {
- static WrappedComponent = WrappedComponent;
-
- static contextType = ReactReduxContext;
-
- static displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
-
- constructor(props, context) {
- super(props, context);
-
- this.injectors = getInjectors(context.store);
-
- this.injectors.injectSaga(key, { saga, mode }, this.props);
- }
-
- componentWillUnmount() {
- this.injectors.ejectSaga(key);
- }
+ const InjectSaga = (props) => {
+ const { store } = useContext(ReactReduxContext);
+ const injectors = getInjectors(store);
+
+ useEffect(() => {
+ injectors.injectSaga(key, { saga, mode }, props);
+ return () => {
+ injectors.ejectSaga(key);
+ };
+ }, []);
+
+ return ;
+ };
- render() {
- return ;
- }
- }
+ InjectSaga.WrappedComponent = WrappedComponent;
+ InjectSaga.displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return hoistNonReactStatics(InjectSaga, WrappedComponent);
};
const useInjectSaga = ({ key, saga, mode }) => {
- const context = React.useContext(ReactReduxContext);
- React.useEffect(() => {
- const injectors = getInjectors(context.store);
+ const { store } = useContext(ReactReduxContext);
+ const injectors = getInjectors(store);
+
+ useEffect(() => {
injectors.injectSaga(key, { saga, mode });
return () => {
injectors.ejectSaga(key);
@@ -56,4 +50,4 @@ const useInjectSaga = ({ key, saga, mode }) => {
}, []);
};
-export { useInjectSaga };
+export { withSaga, useInjectSaga };
diff --git a/package.json b/package.json
index cc1e74c..cd904b6 100644
--- a/package.json
+++ b/package.json
@@ -102,6 +102,7 @@
"eslint-config-standard": "17.0.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-webpack": "0.13.2",
+ "eslint-plugin-immutable": "github:alichherawalla/eslint-plugin-immutable.git#6af48f5498ca1912b618c16bceab8c5464a92f1c",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^26.4.5",
"eslint-plugin-jsx-a11y": "^6.5.1",
diff --git a/pages/_app.js b/pages/_app.js
index a952d30..78698c3 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -1,5 +1,5 @@
+import React from 'react';
import { IntlProvider } from 'react-intl';
-import App from 'next/app';
import { ThemeProvider } from 'styled-components';
import colors from '@themes/colors';
import globalStyle from '@app/global-styles';
@@ -12,27 +12,23 @@ const theme = {
colors
};
-class MyApp extends App {
- static getInitialProps = async ({ Component, ctx }) => {
- return {
- pageProps: {
- ...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
- pathname: ctx.pathname
- }
- };
- };
+const MyApp = ({ Component, pageProps }) => {
+ return (
+
+
+
+
+
+
+ );
+};
- render() {
- const { Component, pageProps } = this.props;
- return (
-
-
-
-
-
-
- );
+MyApp.getInitialProps = async ({ Component, ctx }) => {
+ let pageProps = {};
+ if (Component.getInitialProps) {
+ pageProps = await Component.getInitialProps(ctx);
}
-}
+ return { pageProps };
+};
export default wrapper.withRedux(MyApp);
diff --git a/pages/_document.js b/pages/_document.js
index c30311e..87b06b2 100644
--- a/pages/_document.js
+++ b/pages/_document.js
@@ -2,50 +2,52 @@ import Document, { Head, Html, Main, NextScript } from 'next/document';
import Script from 'next/script';
import { ServerStyleSheet } from 'styled-components';
-export default class MyDocument extends Document {
- static async getInitialProps(ctx) {
- const sheet = new ServerStyleSheet();
- const originalRenderPage = ctx.renderPage;
- try {
- ctx.renderPage = () =>
- originalRenderPage({
- enhanceApp: (App) => (props) => sheet.collectStyles()
- });
+const MyDocument = ({ localeDataScript, locale, styles }) => {
+ // Polyfill Intl API for older browsers
+ const polyfill = `https://cdn.polyfill.io/v3/polyfill.min.js?features=Intl.~locale.${locale}`;
- const initialProps = await Document.getInitialProps(ctx);
- return {
- ...initialProps,
- styles: (
- <>
- {initialProps.styles}
- {sheet.getStyleElement()}
- >
- )
- };
- } finally {
- sheet.seal();
- }
- }
+ return (
+
+
+
+
+
+
+
+ {styles}
+
+
+ );
+};
+
+MyDocument.getInitialProps = async (ctx) => {
+ const sheet = new ServerStyleSheet();
+ const originalRenderPage = ctx.renderPage;
- render() {
- // Polyfill Intl API for older browsers
- const polyfill = `https://cdn.polyfill.io/v3/polyfill.min.js?features=Intl.~locale.${this.props.locale}`;
+ try {
+ ctx.renderPage = () =>
+ originalRenderPage({
+ enhanceApp: (App) => (props) => sheet.collectStyles()
+ });
- return (
-
-
-
-
-
-
-
-
- );
+ const initialProps = await Document.getInitialProps(ctx);
+ return {
+ ...initialProps,
+ styles: (
+ <>
+ {initialProps.styles}
+ {sheet.getStyleElement()}
+ >
+ )
+ };
+ } finally {
+ sheet.seal();
}
-}
+};
+
+export default MyDocument;
diff --git a/yarn.lock b/yarn.lock
index a7d7e8b..7c1ff0b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3994,6 +3994,10 @@ eslint-plugin-es@^3.0.0:
eslint-utils "^2.0.0"
regexpp "^3.0.0"
+"eslint-plugin-immutable@github:alichherawalla/eslint-plugin-immutable.git#6af48f5498ca1912b618c16bceab8c5464a92f1c":
+ version "1.0.0"
+ resolved "https://codeload.github.com/alichherawalla/eslint-plugin-immutable/tar.gz/6af48f5498ca1912b618c16bceab8c5464a92f1c"
+
eslint-plugin-import@2.17.2:
version "2.17.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb"
From 1b5b662a0fcf8cbfcf1a1c332d3c01002ce90c31 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Mon, 18 Mar 2024 15:55:08 +0530
Subject: [PATCH 2/7] sagaInjectors updated
---
app/components/ErrorState/index.js | 45 ++++++++++++----------
app/utils/injectSaga.js | 2 +-
app/utils/sagaInjectors.js | 62 ++++++++++++++++++------------
3 files changed, 64 insertions(+), 45 deletions(-)
diff --git a/app/components/ErrorState/index.js b/app/components/ErrorState/index.js
index 9f3bdda..be7fa75 100644
--- a/app/components/ErrorState/index.js
+++ b/app/components/ErrorState/index.js
@@ -14,27 +14,32 @@ import { CustomCard } from '../styled/repos';
const ErrorState = (props) => {
const { intl, reposError, loading, reposData } = props;
- let repoError;
- if (reposError) {
- repoError = reposError;
- } else if (!get(reposData, 'totalCount', 0)) {
- repoError = 'respo_search_default';
- }
- if (!loading && !repoError) {
+ const getRepoError = () => {
+ if (reposError) {
+ return reposError;
+ } else if (!get(reposData, 'totalCount', 0)) {
+ return 'respo_search_default';
+ }
return null;
- }
- return (
- !loading &&
- repoError && (
-
-
-
- )
- );
+ };
+
+ const renderErrorCard = (repoError) => {
+ return (
+ !loading &&
+ repoError && (
+
+
+
+ )
+ );
+ };
+
+ const repoError = getRepoError();
+ return renderErrorCard(repoError);
};
ErrorState.propTypes = {
diff --git a/app/utils/injectSaga.js b/app/utils/injectSaga.js
index cb5eecd..0f13ac4 100644
--- a/app/utils/injectSaga.js
+++ b/app/utils/injectSaga.js
@@ -50,4 +50,4 @@ const useInjectSaga = ({ key, saga, mode }) => {
}, []);
};
-export { withSaga, useInjectSaga };
+export default { withSaga, useInjectSaga };
diff --git a/app/utils/sagaInjectors.js b/app/utils/sagaInjectors.js
index 4fab1a5..fafc61a 100644
--- a/app/utils/sagaInjectors.js
+++ b/app/utils/sagaInjectors.js
@@ -1,6 +1,5 @@
import invariant from 'invariant';
import { isEmpty, isFunction, isString, conformsTo } from 'lodash';
-
import checkStore from './checkStore';
import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from './constants';
@@ -17,6 +16,36 @@ const checkDescriptor = (descriptor) => {
invariant(conformsTo(descriptor, shape), '(app/utils...) injectSaga: Expected a valid saga descriptor');
};
+function shouldInjectSaga(store, key, newDescriptor) {
+ const hasSaga = Reflect.has(store.injectedSagas, key);
+
+ if (!hasSaga) {
+ return true;
+ }
+
+ if (process.env.NODE_ENV !== 'production') {
+ return shouldCancelOldSaga(store, key, newDescriptor);
+ }
+
+ return newDescriptor.mode !== DAEMON && newDescriptor.mode !== ONCE_TILL_UNMOUNT;
+}
+
+function shouldCancelOldSaga(store, key, newDescriptor) {
+ const oldDescriptor = store.injectedSagas[key];
+ if (oldDescriptor.saga !== newDescriptor.saga) {
+ oldDescriptor.task.cancel();
+ return false;
+ }
+ return true;
+}
+
+function runSaga(store, { key, descriptor, args }) {
+ store.injectedSagas[key] = {
+ ...descriptor,
+ task: store.runSaga(descriptor.saga, args)
+ };
+}
+
export function injectSagaFactory(store, isValid) {
return function injectSaga(key, descriptor = {}, args) {
if (!isValid) {
@@ -27,33 +56,20 @@ export function injectSagaFactory(store, isValid) {
...descriptor,
mode: descriptor.mode || DAEMON
};
- const { saga, mode } = newDescriptor;
checkKey(key);
checkDescriptor(newDescriptor);
- let hasSaga = Reflect.has(store.injectedSagas, key);
-
- if (process.env.NODE_ENV !== 'production') {
- const oldDescriptor = store.injectedSagas[key];
- // enable hot reloading of daemon and once-till-unmount sagas
- if (hasSaga && oldDescriptor.saga !== saga) {
- oldDescriptor.task.cancel();
- hasSaga = false;
- }
- }
-
- if (!hasSaga || (hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT)) {
- /* eslint-disable no-param-reassign */
- store.injectedSagas[key] = {
- ...newDescriptor,
- task: store.runSaga(saga, args)
- };
- /* eslint-enable no-param-reassign */
+ if (shouldInjectSaga(store, key, newDescriptor)) {
+ runSaga(store, { key, descriptor: newDescriptor, args });
}
};
}
+function cancelSaga(task) {
+ task.cancel();
+}
+
export function ejectSagaFactory(store, isValid) {
return function ejectSaga(key) {
if (!isValid) {
@@ -65,11 +81,9 @@ export function ejectSagaFactory(store, isValid) {
if (Reflect.has(store.injectedSagas, key)) {
const descriptor = store.injectedSagas[key];
if (descriptor.mode && descriptor.mode !== DAEMON) {
- descriptor.task.cancel();
- // Clean up in production; in development we need `descriptor.saga` for hot reloading
+ cancelSaga(descriptor.task);
if (process.env.NODE_ENV === 'production') {
- // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga`
- store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign
+ store.injectedSagas[key] = 'done';
}
}
}
From ac895f9fba28398f97b4070459b663ede0aa7557 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Mon, 18 Mar 2024 17:14:26 +0530
Subject: [PATCH 3/7] minor changes
---
.eslintrc.js | 2 +-
app/components/RepoList/index.js | 2 +-
app/utils/sagaInjectors.js | 23 +++++++++++++++--------
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index e3e0106..aaf5843 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -21,7 +21,7 @@ module.exports = {
'max-lines': ['error', { max: 300, skipBlankLines: true, skipComments: true }],
'max-lines-per-function': ['error', 250],
'no-else-return': 'error',
- 'max-params': ['error', 3],
+ 'max-params': ['error', 4],
'no-shadow': 'error',
complexity: ['error', 5],
'no-empty': 'error',
diff --git a/app/components/RepoList/index.js b/app/components/RepoList/index.js
index 2ce309b..3af7d34 100644
--- a/app/components/RepoList/index.js
+++ b/app/components/RepoList/index.js
@@ -19,7 +19,7 @@ const RepoList = (props) => {
const items = get(reposData, 'items', []);
const totalCount = get(reposData, 'totalCount', 0);
- const BlockText = (props) => ;
+ const BlockText = (blockTextProps) => ;
return (
diff --git a/app/utils/sagaInjectors.js b/app/utils/sagaInjectors.js
index fafc61a..6c102cf 100644
--- a/app/utils/sagaInjectors.js
+++ b/app/utils/sagaInjectors.js
@@ -70,6 +70,10 @@ function cancelSaga(task) {
task.cancel();
}
+function shouldCancelSaga(descriptor) {
+ return descriptor.mode && descriptor.mode !== DAEMON;
+}
+
export function ejectSagaFactory(store, isValid) {
return function ejectSaga(key) {
if (!isValid) {
@@ -78,14 +82,17 @@ export function ejectSagaFactory(store, isValid) {
checkKey(key);
- if (Reflect.has(store.injectedSagas, key)) {
- const descriptor = store.injectedSagas[key];
- if (descriptor.mode && descriptor.mode !== DAEMON) {
- cancelSaga(descriptor.task);
- if (process.env.NODE_ENV === 'production') {
- store.injectedSagas[key] = 'done';
- }
- }
+ if (!Reflect.has(store.injectedSagas, key)) {
+ return;
+ }
+
+ const descriptor = store.injectedSagas[key];
+ if (shouldCancelSaga(descriptor)) {
+ cancelSaga(descriptor.task);
+ }
+
+ if (process.env.NODE_ENV === 'production') {
+ store.injectedSagas[key] = 'done';
}
};
}
From 6c4c47066c394630dfa3bd4cb8a54d3a5b1ddfa1 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Tue, 19 Mar 2024 12:05:39 +0530
Subject: [PATCH 4/7] minor change in injectsaga
---
app/utils/injectSaga.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/utils/injectSaga.js b/app/utils/injectSaga.js
index 0f13ac4..d1c1d3b 100644
--- a/app/utils/injectSaga.js
+++ b/app/utils/injectSaga.js
@@ -50,4 +50,5 @@ const useInjectSaga = ({ key, saga, mode }) => {
}, []);
};
-export default { withSaga, useInjectSaga };
+export default withSaga;
+export { useInjectSaga };
From cbade11efa3f52d4f7c27048d5eaea8b0860d76c Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Tue, 19 Mar 2024 13:19:48 +0530
Subject: [PATCH 5/7] sagaInjector complexity fixed
---
.eslintrc.js | 2 +-
app/utils/injectSaga.js | 51 ++++++++++++----------
app/utils/sagaInjectors.js | 89 ++++++++++++++++++--------------------
3 files changed, 72 insertions(+), 70 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index aaf5843..72258ad 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -26,7 +26,7 @@ module.exports = {
complexity: ['error', 5],
'no-empty': 'error',
'import/order': ['error', { groups: [['builtin', 'external', 'internal', 'parent', 'sibling', 'index']] }],
- 'immutable/no-this': 2,
+ // 'immutable/no-this': 2,
'eslint-comments/no-use': 0
},
globals: {
diff --git a/app/utils/injectSaga.js b/app/utils/injectSaga.js
index d1c1d3b..ba34014 100644
--- a/app/utils/injectSaga.js
+++ b/app/utils/injectSaga.js
@@ -1,6 +1,7 @@
-import React, { useContext, useEffect } from 'react';
+import React from 'react';
import hoistNonReactStatics from 'hoist-non-react-statics';
import { ReactReduxContext } from 'react-redux';
+
import getInjectors from './sagaInjectors';
/**
@@ -15,34 +16,39 @@ import getInjectors from './sagaInjectors';
* - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again.
*
*/
-const withSaga =
- ({ key, saga, mode }) =>
+export default ({ key, saga, mode }) =>
(WrappedComponent) => {
- const InjectSaga = (props) => {
- const { store } = useContext(ReactReduxContext);
- const injectors = getInjectors(store);
-
- useEffect(() => {
- injectors.injectSaga(key, { saga, mode }, props);
- return () => {
- injectors.ejectSaga(key);
- };
- }, []);
-
- return ;
- };
+ class InjectSaga extends React.Component {
+ static WrappedComponent = WrappedComponent;
+
+ static contextType = ReactReduxContext;
+
+ static displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
+
+ constructor(props, context) {
+ super(props, context);
- InjectSaga.WrappedComponent = WrappedComponent;
- InjectSaga.displayName = `withSaga(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
+ this.injectors = getInjectors(context.store);
+
+ this.injectors.injectSaga(key, { saga, mode }, this.props);
+ }
+
+ componentWillUnmount() {
+ this.injectors.ejectSaga(key);
+ }
+
+ render() {
+ return ;
+ }
+ }
return hoistNonReactStatics(InjectSaga, WrappedComponent);
};
const useInjectSaga = ({ key, saga, mode }) => {
- const { store } = useContext(ReactReduxContext);
- const injectors = getInjectors(store);
-
- useEffect(() => {
+ const context = React.useContext(ReactReduxContext);
+ React.useEffect(() => {
+ const injectors = getInjectors(context.store);
injectors.injectSaga(key, { saga, mode });
return () => {
injectors.ejectSaga(key);
@@ -50,5 +56,4 @@ const useInjectSaga = ({ key, saga, mode }) => {
}, []);
};
-export default withSaga;
export { useInjectSaga };
diff --git a/app/utils/sagaInjectors.js b/app/utils/sagaInjectors.js
index 6c102cf..3c2cbac 100644
--- a/app/utils/sagaInjectors.js
+++ b/app/utils/sagaInjectors.js
@@ -1,5 +1,6 @@
import invariant from 'invariant';
import { isEmpty, isFunction, isString, conformsTo } from 'lodash';
+
import checkStore from './checkStore';
import { DAEMON, ONCE_TILL_UNMOUNT, RESTART_ON_REMOUNT } from './constants';
@@ -16,37 +17,30 @@ const checkDescriptor = (descriptor) => {
invariant(conformsTo(descriptor, shape), '(app/utils...) injectSaga: Expected a valid saga descriptor');
};
-function shouldInjectSaga(store, key, newDescriptor) {
- const hasSaga = Reflect.has(store.injectedSagas, key);
-
- if (!hasSaga) {
- return true;
- }
-
- if (process.env.NODE_ENV !== 'production') {
- return shouldCancelOldSaga(store, key, newDescriptor);
- }
-
- return newDescriptor.mode !== DAEMON && newDescriptor.mode !== ONCE_TILL_UNMOUNT;
-}
+export function injectSagaFactory(store, isValid) {
+ const updateHasSagaInDevelopment = (hasSaga, key, saga) => {
+ const oldDescriptor = store.injectedSagas[key];
+ // enable hot reloading of daemon and once-till-unmount sagas
+ if (hasSaga && oldDescriptor.saga !== saga) {
+ oldDescriptor.task.cancel();
+ return false;
+ }
+ return hasSaga;
+ };
-function shouldCancelOldSaga(store, key, newDescriptor) {
- const oldDescriptor = store.injectedSagas[key];
- if (oldDescriptor.saga !== newDescriptor.saga) {
- oldDescriptor.task.cancel();
- return false;
- }
- return true;
-}
+ const updateStoreInjectors = (newDescriptor, saga, key, args) => {
+ store.injectedSagas[key] = {
+ ...newDescriptor,
+ task: store.runSaga(saga, args)
+ };
+ };
-function runSaga(store, { key, descriptor, args }) {
- store.injectedSagas[key] = {
- ...descriptor,
- task: store.runSaga(descriptor.saga, args)
+ const checkAndUpdateStoreInjectors = (hasSaga, key, newDescriptor, args) => {
+ if (!hasSaga || (hasSaga && newDescriptor.mode !== DAEMON && newDescriptor.mode !== ONCE_TILL_UNMOUNT)) {
+ updateStoreInjectors(newDescriptor, newDescriptor.saga, key, args);
+ }
};
-}
-export function injectSagaFactory(store, isValid) {
return function injectSaga(key, descriptor = {}, args) {
if (!isValid) {
checkStore(store);
@@ -60,21 +54,27 @@ export function injectSagaFactory(store, isValid) {
checkKey(key);
checkDescriptor(newDescriptor);
- if (shouldInjectSaga(store, key, newDescriptor)) {
- runSaga(store, { key, descriptor: newDescriptor, args });
+ let hasSaga = Reflect.has(store.injectedSagas, key);
+
+ if (process.env.NODE_ENV !== 'production') {
+ hasSaga = updateHasSagaInDevelopment(hasSaga, key, newDescriptor.saga);
}
- };
-}
-function cancelSaga(task) {
- task.cancel();
-}
+ checkAndUpdateStoreInjectors(hasSaga, key, newDescriptor, args);
-function shouldCancelSaga(descriptor) {
- return descriptor.mode && descriptor.mode !== DAEMON;
+ return key;
+ };
}
export function ejectSagaFactory(store, isValid) {
+ function updateStoreSaga(key) {
+ // Clean up in production; in development we need `descriptor.saga` for hot reloading
+ if (process.env.NODE_ENV === 'production') {
+ // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga`
+ store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign
+ }
+ }
+
return function ejectSaga(key) {
if (!isValid) {
checkStore(store);
@@ -82,17 +82,14 @@ export function ejectSagaFactory(store, isValid) {
checkKey(key);
- if (!Reflect.has(store.injectedSagas, key)) {
- return;
- }
+ if (Reflect.has(store.injectedSagas, key)) {
+ const descriptor = store.injectedSagas[key];
+ if (descriptor.mode && descriptor.mode === DAEMON) {
+ return;
+ }
- const descriptor = store.injectedSagas[key];
- if (shouldCancelSaga(descriptor)) {
- cancelSaga(descriptor.task);
- }
-
- if (process.env.NODE_ENV === 'production') {
- store.injectedSagas[key] = 'done';
+ descriptor.task.cancel();
+ updateStoreSaga(key);
}
};
}
From 29e84bac5e5d99a6a940eedebd7a1dbc61714bc8 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Tue, 19 Mar 2024 16:02:11 +0530
Subject: [PATCH 6/7] feat - jsdoc added
---
.eslintrc.js | 13 ++++++++++++-
app/components/Clickable/index.js | 6 ++++++
app/components/Meta/index.js | 6 ++++++
app/components/Text/index.js | 11 +++++++++++
app/components/Title/index.js | 8 ++++++++
app/configureStore.js | 6 ++++++
app/containers/Info/index.js | 15 +++++++++++++++
app/containers/Info/saga.js | 11 +++++++++++
app/containers/Repos/index.js | 18 ++++++++++++++++++
app/containers/Repos/saga.js | 11 +++++++++++
app/reducers.js | 3 +++
app/utils/index.js | 5 +++++
app/utils/sagaInjectors.js | 21 +++++++++++++++++++++
pages/index.js | 9 +++++++++
pages/info/[name].js | 13 +++++++++++++
15 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 72258ad..5304585 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -22,11 +22,22 @@ module.exports = {
'max-lines-per-function': ['error', 250],
'no-else-return': 'error',
'max-params': ['error', 4],
+ 'require-jsdoc': [
+ 'error',
+ {
+ require: {
+ FunctionDeclaration: true,
+ MethodDefinition: false,
+ ClassDeclaration: false,
+ ArrowFunctionExpression: false,
+ FunctionExpression: false
+ }
+ }
+ ],
'no-shadow': 'error',
complexity: ['error', 5],
'no-empty': 'error',
'import/order': ['error', { groups: [['builtin', 'external', 'internal', 'parent', 'sibling', 'index']] }],
- // 'immutable/no-this': 2,
'eslint-comments/no-use': 0
},
globals: {
diff --git a/app/components/Clickable/index.js b/app/components/Clickable/index.js
index 2fb3a26..7e56ec4 100644
--- a/app/components/Clickable/index.js
+++ b/app/components/Clickable/index.js
@@ -15,6 +15,12 @@ const StyledClickable = styled.div`
cursor: pointer;
}
`;
+
+/**
+ * A component that can be clicked
+ * @param {function} onClick - The function to call when the component is clicked
+ * @param {string} textId - The id of the text to display
+ */
function Clickable({ onClick, textId }) {
return (
diff --git a/app/components/Meta/index.js b/app/components/Meta/index.js
index 33b0029..522983b 100644
--- a/app/components/Meta/index.js
+++ b/app/components/Meta/index.js
@@ -10,6 +10,12 @@ import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import favicon from '@images/favicon.ico';
+/**
+ * The Meta component
+ * @param {string} title - The title of the page
+ * @param {string} description - The description of the page
+ * @param {boolean} useTranslation - Whether to use translation for the title and description
+ */
function Meta({ title, description, useTranslation }) {
const intl = useIntl();
diff --git a/app/components/Text/index.js b/app/components/Text/index.js
index d25bd60..3926204 100644
--- a/app/components/Text/index.js
+++ b/app/components/Text/index.js
@@ -17,6 +17,17 @@ const StyledText = styled.span`
${(props) => props.fontweight};
${(props) => props.styles};
`;
+
+/**
+ * A component for displaying text
+ * @param {string} id - The id of the text to display
+ * @param {string} text - The text to display
+ * @param {object} values - The values to pass to the text
+ * @param {string} color - The color of the text
+ * @param {string} fontWeight - The font weight of the text
+ * @param {string} fontSize - The font size of the text
+ * @param {string} display - The display type of the text
+ */
function Text({ id = 'default', text, values = {}, children, color, fontWeight, fontSize, ...props }) {
return (
diff --git a/app/components/Title/index.js b/app/components/Title/index.js
index a3f3704..65addd2 100644
--- a/app/components/Title/index.js
+++ b/app/components/Title/index.js
@@ -11,6 +11,14 @@ import { StarOutlined } from '@ant-design/icons';
import Text from '@app/components/Text/index';
import fonts from '@app/themes/fonts';
+/**
+ * The title of the info container
+ * @param {object} props The component props
+ * @param {string} props.name The name of the repo
+ * @param {boolean} props.loading Whether the data is loading
+ * @param {number} props.stargazersCount The number of stargazers
+ * @returns {JSX.Element} The title of the info container
+ */
function Title(props) {
const { name, loading, stargazersCount } = props;
const headingStyle = fonts.style.heading();
diff --git a/app/configureStore.js b/app/configureStore.js
index 7c693ca..c24b819 100644
--- a/app/configureStore.js
+++ b/app/configureStore.js
@@ -8,6 +8,12 @@ import { createWrapper } from 'next-redux-wrapper';
import createReducer from './reducers';
+/**
+ *
+ * @param {object} initialState The initial state
+ * @returns {object} The store
+ *
+ */
export default function configureStore(initialState = {}) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
diff --git a/app/containers/Info/index.js b/app/containers/Info/index.js
index 6ee9d4b..17f6dca 100644
--- a/app/containers/Info/index.js
+++ b/app/containers/Info/index.js
@@ -22,6 +22,16 @@ import { infoCreators } from './reducer';
import saga from './saga';
import { selectInfoData, selectInfoLoading } from './selectors';
+/**
+ * The Info container
+ * @param {object} props The component props
+ * @param {object} props.details The details of the repo
+ * @param {object} props.params The params from the route
+ * @param {boolean} props.loading Whether the data is loading
+ * @param {function} props.dispatchRequestInfo The function to request the info
+ * @param {object} props.fallBackDetails The details to fall back on
+ * @returns {JSX.Element} The Info container
+ */
export function Info({ details, params, loading, dispatchRequestInfo, fallBackDetails }) {
const router = useRouter();
const { query } = router;
@@ -74,6 +84,11 @@ const mapStateToProps = createStructuredSelector({
fallBackDetails: selectInfoData()
});
+/**
+ * The mapDispatchToProps
+ * @param {function} dispatch The dispatch function
+ * @returns {object} The props
+ */
function mapDispatchToProps(dispatch) {
return {
dispatchRequestInfo: (repo, owner) => dispatch(infoCreators.requestInfo(repo, owner))
diff --git a/app/containers/Info/saga.js b/app/containers/Info/saga.js
index 5865a0b..fd22122 100644
--- a/app/containers/Info/saga.js
+++ b/app/containers/Info/saga.js
@@ -3,6 +3,13 @@ import { getRepo } from '@services/info';
import { ERRORS } from '@app/utils/constants';
import { infoTypes, infoCreators, INFO_PAYLOAD } from './reducer';
+/**
+ * Request info from the API
+ * @param {object} action
+ * @param {string} action[INFO_PAYLOAD.REPO] - The name of the repository
+ * @param {string} action[INFO_PAYLOAD.OWNER] - The owner of the repository
+ * @returns {object} - The response from the API
+ */
export function* requestInfo(action) {
try {
if (!action[INFO_PAYLOAD.REPO] || !action[INFO_PAYLOAD.OWNER]) {
@@ -16,6 +23,10 @@ export function* requestInfo(action) {
}
}
+/**
+ * The root of the info saga
+ * @returns {void}
+ */
export default function* appSaga() {
yield takeLatest(infoTypes.REQUEST_INFO, requestInfo);
}
diff --git a/app/containers/Repos/index.js b/app/containers/Repos/index.js
index e6ba357..fc83dbb 100644
--- a/app/containers/Repos/index.js
+++ b/app/containers/Repos/index.js
@@ -25,6 +25,19 @@ import { reposActionCreators } from './reducer';
import saga from './saga';
import { selectReposData, selectReposError, selectReposSearchKey } from './selectors';
+/**
+ * The Repos container
+ * @param {object} props The component props
+ * @param {object} props.intl The intl object
+ * @param {string} props.searchKey The search key
+ * @param {object} props.repos The repos data
+ * @param {string} props.error The error message
+ * @param {boolean} props.loading Whether the data is loading
+ * @param {object} props.recommendations The list of recommendations
+ * @param {function} props.dispatchGetGithubRepos The function to get the github repos
+ * @param {function} props.dispatchClearGithubRepos The function to clear the github repos
+ * @returns {JSX.Element} The Repos container
+ */
export function Repos({
intl,
repos,
@@ -111,6 +124,11 @@ const mapStateToProps = createStructuredSelector({
searchKey: selectReposSearchKey()
});
+/**
+ * The mapDispatchToProps
+ * @param {function} dispatch The dispatch function
+ * @returns {object} The props
+ */
function mapDispatchToProps(dispatch) {
const { requestGetGithubRepos, clearGithubRepos } = reposActionCreators;
return {
diff --git a/app/containers/Repos/saga.js b/app/containers/Repos/saga.js
index c0c51fe..c74f29e 100644
--- a/app/containers/Repos/saga.js
+++ b/app/containers/Repos/saga.js
@@ -5,6 +5,12 @@ import { reposActionTypes, reposActionCreators, REPOS_PAYLOAD } from './reducer'
const { REQUEST_GET_GITHUB_REPOS } = reposActionTypes;
const { successGetGithubRepos, failureGetGithubRepos } = reposActionCreators;
+/**
+ * Get the github repos
+ * @param {object} action
+ * @param {string} action[REPOS_PAYLOAD.SEARCH_KEY] - The search key
+ * @returns {object} - The response from the API
+ */
export function* getGithubRepos(action) {
const response = yield call(getRepos, action[REPOS_PAYLOAD.SEARCH_KEY]);
const { data, ok } = response;
@@ -15,6 +21,11 @@ export function* getGithubRepos(action) {
}
}
+/**
+ * The root of the repos saga
+ * @returns {void}
+ * @yields {object} - The response from the API
+ */
export default function* appSaga() {
yield takeLatest(REQUEST_GET_GITHUB_REPOS, getGithubRepos);
}
diff --git a/app/reducers.js b/app/reducers.js
index 7592723..eb74141 100644
--- a/app/reducers.js
+++ b/app/reducers.js
@@ -10,6 +10,9 @@ import info from './containers/Info/reducer';
enableAllPlugins();
+/**
+ * Merges the main reducer with the router state and dynamically injected reducers
+ */
export default function createReducer(injectedReducer = {}) {
const rootReducer = combineReducers({
...injectedReducer,
diff --git a/app/utils/index.js b/app/utils/index.js
index 852c625..1ee08a5 100644
--- a/app/utils/index.js
+++ b/app/utils/index.js
@@ -1,6 +1,11 @@
import pickBy from 'lodash/pickBy';
import { screenSizes } from '@themes/media';
+/**
+ * Get query string value
+ * @param {array} keys - The keys to get the value of
+ * @returns {object} - The query string value
+ */
export function getQueryStringValue(keys) {
const queryString = {};
try {
diff --git a/app/utils/sagaInjectors.js b/app/utils/sagaInjectors.js
index 3c2cbac..01df7ba 100644
--- a/app/utils/sagaInjectors.js
+++ b/app/utils/sagaInjectors.js
@@ -17,6 +17,12 @@ const checkDescriptor = (descriptor) => {
invariant(conformsTo(descriptor, shape), '(app/utils...) injectSaga: Expected a valid saga descriptor');
};
+/**
+ * Validate the saga, mode and key
+ * @param {object} descriptor The saga descriptor
+ * @param {string} key The saga key
+ * @param {object} saga The saga
+ */
export function injectSagaFactory(store, isValid) {
const updateHasSagaInDevelopment = (hasSaga, key, saga) => {
const oldDescriptor = store.injectedSagas[key];
@@ -66,7 +72,18 @@ export function injectSagaFactory(store, isValid) {
};
}
+/**
+ * Eject the saga
+ * @param {string} key The saga key
+ * @param {object} store The redux store
+ * @param {boolean} isValid If the store is valid
+ */
export function ejectSagaFactory(store, isValid) {
+ /**
+ * Clean up the store
+ * @param {string} key The saga key
+ * @returns {void}
+ */
function updateStoreSaga(key) {
// Clean up in production; in development we need `descriptor.saga` for hot reloading
if (process.env.NODE_ENV === 'production') {
@@ -94,6 +111,10 @@ export function ejectSagaFactory(store, isValid) {
};
}
+/**
+ * Get the injectors
+ * @param {object} store The redux store
+ */
export default function getInjectors(store) {
checkStore(store);
return {
diff --git a/pages/index.js b/pages/index.js
index e79c661..28645f5 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -2,6 +2,10 @@ import PropTypes from 'prop-types';
import Repos from '@app/containers/Repos/index';
import { getReccomendations } from '@services/root';
+/**
+ * Get the list of recommendations
+ * @returns {object} The list of recommendations
+ */
export async function getStaticProps() {
const recommendations = await getReccomendations();
return {
@@ -11,6 +15,11 @@ export async function getStaticProps() {
};
}
+/**
+ * The ReposPage component
+ * @param {object} props The component props
+ * @param {object} props.recommendations The list of recommendations
+ */
export function ReposPage({ recommendations = [] }) {
return ;
}
diff --git a/pages/info/[name].js b/pages/info/[name].js
index 68c8178..e148644 100644
--- a/pages/info/[name].js
+++ b/pages/info/[name].js
@@ -6,6 +6,15 @@ const RepoInfo = ({ details }) => ;
export default RepoInfo;
+/**
+ * Get the details of the repo
+ * @param {object} props The component props
+ * @param {object} props.params The route parameters
+ * @returns {object} The details of the repo
+ * @returns {string} The details.name The name of the repo
+ * @returns {string} The details.description The description of the repo
+ * @returns {number} The details.stargazersCount The number of stargazers
+ */
export async function getStaticProps(props) {
const {
params: { name }
@@ -18,6 +27,10 @@ export async function getStaticProps(props) {
};
}
+/**
+ * Get the list of recommendations
+ * @returns {object} The list of recommendations
+ */
export async function getStaticPaths() {
const recommendations = await getReccomendations();
// * param value should be a string
From a3ee97ed4aadd286bb8e1b71c6436b12b8620b41 Mon Sep 17 00:00:00 2001
From: Anas WS
Date: Wed, 20 Mar 2024 15:23:24 +0530
Subject: [PATCH 7/7] Minor crc fixed
---
app/components/RepoList/index.js | 4 ++--
app/components/Title/index.js | 2 +-
app/containers/Repos/index.js | 2 +-
app/global-styles.js | 2 +-
pages/index.js | 2 +-
pages/info/[name].js | 2 +-
6 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/components/RepoList/index.js b/app/components/RepoList/index.js
index 3af7d34..731f703 100644
--- a/app/components/RepoList/index.js
+++ b/app/components/RepoList/index.js
@@ -10,8 +10,8 @@ import { Skeleton } from 'antd';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router';
import T from '@components/Text';
-import { CustomCard } from '../styled/repos';
-import If from '../If/index';
+import If from '@components/If';
+import { CustomCard } from '@components/styled/repos';
const RepoList = (props) => {
const { reposData, loading, repoName } = props;
diff --git a/app/components/Title/index.js b/app/components/Title/index.js
index 65addd2..04eddfe 100644
--- a/app/components/Title/index.js
+++ b/app/components/Title/index.js
@@ -8,7 +8,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Row, Skeleton } from 'antd';
import { StarOutlined } from '@ant-design/icons';
-import Text from '@app/components/Text/index';
+import Text from '@app/components/Text';
import fonts from '@app/themes/fonts';
/**
diff --git a/app/containers/Repos/index.js b/app/containers/Repos/index.js
index fc83dbb..86599ee 100644
--- a/app/containers/Repos/index.js
+++ b/app/containers/Repos/index.js
@@ -6,7 +6,7 @@
import Recommended from '@app/components/Recommended';
import { Container } from '@app/components/styled';
import ErrorState from '@components/ErrorState';
-import RepoList from '@components/RepoList/index';
+import RepoList from '@components/RepoList';
import { CustomCard, YouAreAwesome } from '@components/styled/repos';
import T from '@components/Text';
import { fonts } from '@themes';
diff --git a/app/global-styles.js b/app/global-styles.js
index 15b2639..45ea3c1 100644
--- a/app/global-styles.js
+++ b/app/global-styles.js
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
-import { colors } from './themes/index';
+import { colors } from '@themes';
const globalStyle = css`
html,
diff --git a/pages/index.js b/pages/index.js
index 28645f5..3b210b3 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-import Repos from '@app/containers/Repos/index';
+import Repos from '@app/containers/Repos';
import { getReccomendations } from '@services/root';
/**
diff --git a/pages/info/[name].js b/pages/info/[name].js
index e148644..95510d3 100644
--- a/pages/info/[name].js
+++ b/pages/info/[name].js
@@ -1,6 +1,6 @@
import { getRepo } from '@app/services/info';
import { getReccomendations } from '@services/root';
-import Info from '@app/containers/Info/index';
+import Info from '@app/containers/Info';
const RepoInfo = ({ details }) => ;