Index: frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=boolean-prop-naming.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"boolean-prop-naming.d.ts","sourceRoot":"","sources":["boolean-prop-naming.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/boolean-prop-naming.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,426 @@
+/**
+ * @fileoverview Enforces consistent naming for boolean props
+ * @author Ev Haus
+ */
+
+'use strict';
+
+const flatMap = require('array.prototype.flatmap');
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const propsUtil = require('../util/props');
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+/**
+ * Checks if prop is nested
+ * @param {Object} prop Property object, single prop type declaration
+ * @returns {boolean}
+ */
+function nestedPropTypes(prop) {
+  return (
+    prop.type === 'Property'
+    && astUtil.isCallExpression(prop.value)
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      category: 'Stylistic Issues',
+      description: 'Enforces consistent naming for boolean props',
+      recommended: false,
+      url: docsUrl('boolean-prop-naming'),
+    },
+
+    messages,
+
+    schema: [{
+      additionalProperties: false,
+      properties: {
+        propTypeNames: {
+          items: {
+            type: 'string',
+          },
+          minItems: 1,
+          type: 'array',
+          uniqueItems: true,
+        },
+        rule: {
+          default: '^(is|has)[A-Z]([A-Za-z0-9]?)+',
+          minLength: 1,
+          type: 'string',
+        },
+        message: {
+          minLength: 1,
+          type: 'string',
+        },
+        validateNested: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      type: 'object',
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const config = context.options[0] || {};
+    const rule = config.rule ? new RegExp(config.rule) : null;
+    const propTypeNames = config.propTypeNames || ['bool'];
+
+    // Remembers all Flowtype object definitions
+    const objectTypeAnnotations = new Map();
+
+    /**
+     * Returns the prop key to ensure we handle the following cases:
+     * propTypes: {
+     *   full: React.PropTypes.bool,
+     *   short: PropTypes.bool,
+     *   direct: bool,
+     *   required: PropTypes.bool.isRequired
+     * }
+     * @param {Object} node The node we're getting the name of
+     * @returns {string | null}
+     */
+    function getPropKey(node) {
+      // Check for `ExperimentalSpreadProperty` (eslint 3/4) and `SpreadElement` (eslint 5)
+      // so we can skip validation of those fields.
+      // Otherwise it will look for `node.value.property` which doesn't exist and breaks eslint.
+      if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {
+        return null;
+      }
+      if (node.value && node.value.property) {
+        const name = node.value.property.name;
+        if (name === 'isRequired') {
+          if (node.value.object && node.value.object.property) {
+            return node.value.object.property.name;
+          }
+          return null;
+        }
+        return name;
+      }
+      if (node.value && node.value.type === 'Identifier') {
+        return node.value.name;
+      }
+      return null;
+    }
+
+    /**
+     * Returns the name of the given node (prop)
+     * @param {Object} node The node we're getting the name of
+     * @returns {string}
+     */
+    function getPropName(node) {
+      // Due to this bug https://github.com/babel/babel-eslint/issues/307
+      // we can't get the name of the Flow object key name. So we have
+      // to hack around it for now.
+      if (node.type === 'ObjectTypeProperty') {
+        return getSourceCode(context).getFirstToken(node).value;
+      }
+
+      return node.key.name;
+    }
+
+    /**
+     * Checks if prop is declared in flow way
+     * @param {Object} prop Property object, single prop type declaration
+     * @returns {boolean}
+     */
+    function flowCheck(prop) {
+      return (
+        prop.type === 'ObjectTypeProperty'
+        && prop.value.type === 'BooleanTypeAnnotation'
+        && rule.test(getPropName(prop)) === false
+      );
+    }
+
+    /**
+     * Checks if prop is declared in regular way
+     * @param {Object} prop Property object, single prop type declaration
+     * @returns {boolean}
+     */
+    function regularCheck(prop) {
+      const propKey = getPropKey(prop);
+      return (
+        propKey
+        && propTypeNames.indexOf(propKey) >= 0
+        && rule.test(getPropName(prop)) === false
+      );
+    }
+
+    function tsCheck(prop) {
+      if (prop.type !== 'TSPropertySignature') return false;
+      const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation;
+      return (
+        typeAnnotation
+        && typeAnnotation.type === 'TSBooleanKeyword'
+        && rule.test(getPropName(prop)) === false
+      );
+    }
+
+    /**
+     * Runs recursive check on all proptypes
+     * @param {Array} proptypes A list of Property object (for each proptype defined)
+     * @param {Function} addInvalidProp callback to run for each error
+     */
+    function runCheck(proptypes, addInvalidProp) {
+      if (proptypes) {
+        proptypes.forEach((prop) => {
+          if (config.validateNested && nestedPropTypes(prop)) {
+            runCheck(prop.value.arguments[0].properties, addInvalidProp);
+            return;
+          }
+          if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) {
+            addInvalidProp(prop);
+          }
+        });
+      }
+    }
+
+    /**
+     * Checks and mark props with invalid naming
+     * @param {Object} node The component node we're testing
+     * @param {Array} proptypes A list of Property object (for each proptype defined)
+     */
+    function validatePropNaming(node, proptypes) {
+      const component = components.get(node) || node;
+      const invalidProps = component.invalidProps || [];
+
+      runCheck(proptypes, (prop) => {
+        invalidProps.push(prop);
+      });
+
+      components.set(node, {
+        invalidProps,
+      });
+    }
+
+    /**
+     * Reports invalid prop naming
+     * @param {Object} component The component to process
+     */
+    function reportInvalidNaming(component) {
+      component.invalidProps.forEach((propNode) => {
+        const propName = getPropName(propNode);
+        report(context, config.message || messages.patternMismatch, !config.message && 'patternMismatch', {
+          node: propNode,
+          data: {
+            component: propName,
+            propName,
+            pattern: config.rule,
+          },
+        });
+      });
+    }
+
+    function checkPropWrapperArguments(node, args) {
+      if (!node || !Array.isArray(args)) {
+        return;
+      }
+      args.filter((arg) => arg.type === 'ObjectExpression').forEach((object) => validatePropNaming(node, object.properties));
+    }
+
+    function getComponentTypeAnnotation(component) {
+      // If this is a functional component that uses a global type, check it
+      if (
+        (component.node.type === 'FunctionDeclaration' || component.node.type === 'ArrowFunctionExpression')
+        && component.node.params
+        && component.node.params.length > 0
+        && component.node.params[0].typeAnnotation
+      ) {
+        return component.node.params[0].typeAnnotation.typeAnnotation;
+      }
+
+      if (
+        !component.node.parent
+        || component.node.parent.type !== 'VariableDeclarator'
+        || !component.node.parent.id
+        || component.node.parent.id.type !== 'Identifier'
+        || !component.node.parent.id.typeAnnotation
+        || !component.node.parent.id.typeAnnotation.typeAnnotation
+      ) {
+        return;
+      }
+
+      const annotationTypeArguments = propsUtil.getTypeArguments(
+        component.node.parent.id.typeAnnotation.typeAnnotation
+      );
+      if (
+        annotationTypeArguments && (
+          annotationTypeArguments.type === 'TSTypeParameterInstantiation'
+          || annotationTypeArguments.type === 'TypeParameterInstantiation'
+        )
+      ) {
+        return annotationTypeArguments.params.find(
+          (param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation'
+        );
+      }
+    }
+
+    function findAllTypeAnnotations(identifier, node) {
+      if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation' || node.type === 'TSInterfaceBody') {
+        const currentNode = [].concat(
+          objectTypeAnnotations.get(identifier.name) || [],
+          node
+        );
+        objectTypeAnnotations.set(identifier.name, currentNode);
+      } else if (
+        node.type === 'TSParenthesizedType'
+        && (
+          node.typeAnnotation.type === 'TSIntersectionType'
+          || node.typeAnnotation.type === 'TSUnionType'
+        )
+      ) {
+        node.typeAnnotation.types.forEach((type) => {
+          findAllTypeAnnotations(identifier, type);
+        });
+      } else if (
+        node.type === 'TSIntersectionType'
+        || node.type === 'TSUnionType'
+        || node.type === 'IntersectionTypeAnnotation'
+        || node.type === 'UnionTypeAnnotation'
+      ) {
+        node.types.forEach((type) => {
+          findAllTypeAnnotations(identifier, type);
+        });
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
+          return;
+        }
+        if (
+          node.value
+          && astUtil.isCallExpression(node.value)
+          && propWrapperUtil.isPropWrapperFunction(
+            context,
+            getText(context, node.value.callee)
+          )
+        ) {
+          checkPropWrapperArguments(node, node.value.arguments);
+        }
+        if (node.value && node.value.properties) {
+          validatePropNaming(node, node.value.properties);
+        }
+        if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
+          validatePropNaming(node, node.typeAnnotation.typeAnnotation.properties);
+        }
+      },
+
+      MemberExpression(node) {
+        if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
+          return;
+        }
+        const component = utils.getRelatedComponent(node);
+        if (!component || !node.parent.right) {
+          return;
+        }
+        const right = node.parent.right;
+        if (
+          astUtil.isCallExpression(right)
+          && propWrapperUtil.isPropWrapperFunction(
+            context,
+            getText(context, right.callee)
+          )
+        ) {
+          checkPropWrapperArguments(component.node, right.arguments);
+          return;
+        }
+        validatePropNaming(component.node, node.parent.right.properties);
+      },
+
+      ObjectExpression(node) {
+        if (!rule) {
+          return;
+        }
+
+        // Search for the proptypes declaration
+        node.properties.forEach((property) => {
+          if (!propsUtil.isPropTypesDeclaration(property)) {
+            return;
+          }
+          validatePropNaming(node, property.value.properties);
+        });
+      },
+
+      TypeAlias(node) {
+        findAllTypeAnnotations(node.id, node.right);
+      },
+
+      TSTypeAliasDeclaration(node) {
+        findAllTypeAnnotations(node.id, node.typeAnnotation);
+      },
+
+      TSInterfaceDeclaration(node) {
+        findAllTypeAnnotations(node.id, node.body);
+      },
+
+      // eslint-disable-next-line object-shorthand
+      'Program:exit'() {
+        if (!rule) {
+          return;
+        }
+
+        values(components.list()).forEach((component) => {
+          const annotation = getComponentTypeAnnotation(component);
+
+          if (annotation) {
+            let propType;
+            if (annotation.type === 'GenericTypeAnnotation') {
+              propType = objectTypeAnnotations.get(annotation.id.name);
+            } else if (annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'TSTypeLiteral') {
+              propType = annotation;
+            } else if (annotation.type === 'TSTypeReference') {
+              propType = objectTypeAnnotations.get(annotation.typeName.name);
+            } else if (annotation.type === 'TSIntersectionType') {
+              propType = flatMap(annotation.types, (type) => (
+                type.type === 'TSTypeReference'
+                  ? objectTypeAnnotations.get(type.typeName.name)
+                  : type
+              ));
+            }
+
+            if (propType) {
+              [].concat(propType).filter(Boolean).forEach((prop) => {
+                validatePropNaming(
+                  component.node,
+                  prop.properties || prop.members || prop.body
+                );
+              });
+            }
+          }
+
+          if (component.invalidProps && component.invalidProps.length > 0) {
+            reportInvalidNaming(component);
+          }
+        });
+
+        // Reset cache
+        objectTypeAnnotations.clear();
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=button-has-type.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"button-has-type.d.ts","sourceRoot":"","sources":["button-has-type.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/button-has-type.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,169 @@
+/**
+ * @fileoverview Forbid "button" element without an explicit "type" attribute
+ * @author Filipp Riabchun
+ */
+
+'use strict';
+
+const getProp = require('jsx-ast-utils/getProp');
+const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const optionDefaults = {
+  button: true,
+  submit: true,
+  reset: true,
+};
+
+const messages = {
+  missingType: 'Missing an explicit type attribute for button',
+  complexType: 'The button type attribute must be specified by a static string or a trivial ternary expression',
+  invalidValue: '"{{value}}" is an invalid value for button type attribute',
+  forbiddenValue: '"{{value}}" is an invalid value for button type attribute',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of `button` elements without an explicit `type` attribute',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('button-has-type'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        button: {
+          default: optionDefaults.button,
+          type: 'boolean',
+        },
+        submit: {
+          default: optionDefaults.submit,
+          type: 'boolean',
+        },
+        reset: {
+          default: optionDefaults.reset,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = Object.assign({}, optionDefaults, context.options[0]);
+
+    function reportMissing(node) {
+      report(context, messages.missingType, 'missingType', {
+        node,
+      });
+    }
+
+    function reportComplex(node) {
+      report(context, messages.complexType, 'complexType', {
+        node,
+      });
+    }
+
+    function checkValue(node, value) {
+      if (!(value in configuration)) {
+        report(context, messages.invalidValue, 'invalidValue', {
+          node,
+          data: {
+            value,
+          },
+        });
+      } else if (!configuration[value]) {
+        report(context, messages.forbiddenValue, 'forbiddenValue', {
+          node,
+          data: {
+            value,
+          },
+        });
+      }
+    }
+
+    function checkExpression(node, expression) {
+      switch (expression.type) {
+        case 'Literal':
+          checkValue(node, expression.value);
+          return;
+        case 'TemplateLiteral':
+          if (expression.expressions.length === 0) {
+            checkValue(node, expression.quasis[0].value.raw);
+          } else {
+            reportComplex(expression);
+          }
+          return;
+        case 'ConditionalExpression':
+          checkExpression(node, expression.consequent);
+          checkExpression(node, expression.alternate);
+          return;
+        default:
+          reportComplex(expression);
+      }
+    }
+
+    return {
+      JSXElement(node) {
+        if (node.openingElement.name.name !== 'button') {
+          return;
+        }
+
+        const typeProp = getProp(node.openingElement.attributes, 'type');
+
+        if (!typeProp) {
+          reportMissing(node);
+          return;
+        }
+
+        if (typeProp.value && typeProp.value.type === 'JSXExpressionContainer') {
+          checkExpression(node, typeProp.value.expression);
+          return;
+        }
+
+        const propValue = getLiteralPropValue(typeProp);
+        checkValue(node, propValue);
+      },
+      CallExpression(node) {
+        if (!isCreateElement(context, node) || node.arguments.length < 1) {
+          return;
+        }
+
+        if (node.arguments[0].type !== 'Literal' || node.arguments[0].value !== 'button') {
+          return;
+        }
+
+        if (!node.arguments[1] || node.arguments[1].type !== 'ObjectExpression') {
+          reportMissing(node);
+          return;
+        }
+
+        const props = node.arguments[1].properties;
+        const typeProp = props.find((prop) => (
+          'key' in prop
+          && prop.key
+          && 'name' in prop.key
+          && prop.key.name === 'type'
+        ));
+
+        if (!typeProp) {
+          reportMissing(node);
+          return;
+        }
+
+        checkExpression(node, 'value' in typeProp ? typeProp.value : undefined);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=checked-requires-onchange-or-readonly.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"checked-requires-onchange-or-readonly.d.ts","sourceRoot":"","sources":["checked-requires-onchange-or-readonly.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/checked-requires-onchange-or-readonly.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,142 @@
+/**
+ * @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used'
+ * @author Jaesoekjjang
+ */
+
+'use strict';
+
+const ASTUtils = require('jsx-ast-utils');
+const flatMap = require('array.prototype.flatmap');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+const docsUrl = require('../util/docsUrl');
+
+const messages = {
+  missingProperty: '`checked` should be used with either `onChange` or `readOnly`.',
+  exclusiveCheckedAttribute: 'Use either `checked` or `defaultChecked`, but not both.',
+};
+
+const targetPropSet = new Set(['checked', 'onChange', 'readOnly', 'defaultChecked']);
+
+const defaultOptions = {
+  ignoreMissingProperties: false,
+  ignoreExclusiveCheckedAttribute: false,
+};
+
+/**
+ * @param {object[]} properties
+ * @param {string} keyName
+ * @returns {Set<string>}
+ */
+function extractTargetProps(properties, keyName) {
+  return new Set(
+    flatMap(
+      properties,
+      (prop) => (
+        prop[keyName] && targetPropSet.has(prop[keyName].name)
+          ? [prop[keyName].name]
+          : []
+      )
+    )
+  );
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce using `onChange` or `readonly` attribute when `checked` is used',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('checked-requires-onchange-or-readonly'),
+    },
+    messages,
+    schema: [{
+      additionalProperties: false,
+      properties: {
+        ignoreMissingProperties: {
+          type: 'boolean',
+        },
+        ignoreExclusiveCheckedAttribute: {
+          type: 'boolean',
+        },
+      },
+    }],
+  },
+  create(context) {
+    const options = Object.assign({}, defaultOptions, context.options[0]);
+
+    function reportMissingProperty(node) {
+      report(
+        context,
+        messages.missingProperty,
+        'missingProperty',
+        { node }
+      );
+    }
+
+    function reportExclusiveCheckedAttribute(node) {
+      report(
+        context,
+        messages.exclusiveCheckedAttribute,
+        'exclusiveCheckedAttribute',
+        { node }
+      );
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @param {Set<string>} propSet
+     * @returns {void}
+     */
+    const checkAttributesAndReport = (node, propSet) => {
+      if (!propSet.has('checked')) {
+        return;
+      }
+
+      if (!options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) {
+        reportExclusiveCheckedAttribute(node);
+      }
+
+      if (
+        !options.ignoreMissingProperties
+        && !(propSet.has('onChange') || propSet.has('readOnly'))
+      ) {
+        reportMissingProperty(node);
+      }
+    };
+
+    return {
+      JSXOpeningElement(node) {
+        if (ASTUtils.elementType(node) !== 'input') {
+          return;
+        }
+
+        const propSet = extractTargetProps(node.attributes, 'name');
+        checkAttributesAndReport(node, propSet);
+      },
+      CallExpression(node) {
+        if (!isCreateElement(context, node)) {
+          return;
+        }
+
+        const firstArg = node.arguments[0];
+        const secondArg = node.arguments[1];
+        if (
+          !firstArg
+          || firstArg.type !== 'Literal'
+          || firstArg.value !== 'input'
+        ) {
+          return;
+        }
+
+        if (!secondArg || secondArg.type !== 'ObjectExpression') {
+          return;
+        }
+
+        const propSet = extractTargetProps(secondArg.properties, 'key');
+        checkAttributesAndReport(node, propSet);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=default-props-match-prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"default-props-match-prop-types.d.ts","sourceRoot":"","sources":["default-props-match-prop-types.js"],"names":[],"mappings":"wBAuBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/default-props-match-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,109 @@
+/**
+ * @fileOverview Enforce all defaultProps are defined in propTypes
+ * @author Vitor Balocco
+ * @author Roy Sutton
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  requiredHasDefault: 'defaultProp "{{name}}" defined for isRequired propType.',
+  defaultHasNoType: 'defaultProp "{{name}}" has no corresponding propTypes declaration.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce all defaultProps have a corresponding non-required PropType',
+      category: 'Best Practices',
+      url: docsUrl('default-props-match-prop-types'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowRequiredDefaults: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    const configuration = context.options[0] || {};
+    const allowRequiredDefaults = configuration.allowRequiredDefaults || false;
+
+    /**
+     * Reports all defaultProps passed in that don't have an appropriate propTypes counterpart.
+     * @param  {Object[]} propTypes    Array of propTypes to check.
+     * @param  {Object}   defaultProps Object of defaultProps to check. Keys are the props names.
+     * @return {void}
+     */
+    function reportInvalidDefaultProps(propTypes, defaultProps) {
+      // If this defaultProps is "unresolved" or the propTypes is undefined, then we should ignore
+      // this component and not report any errors for it, to avoid false-positives with e.g.
+      // external defaultProps/propTypes declarations or spread operators.
+      if (defaultProps === 'unresolved' || !propTypes || Object.keys(propTypes).length === 0) {
+        return;
+      }
+
+      Object.keys(defaultProps).forEach((defaultPropName) => {
+        const defaultProp = defaultProps[defaultPropName];
+        const prop = propTypes[defaultPropName];
+
+        if (prop && (allowRequiredDefaults || !prop.isRequired)) {
+          return;
+        }
+
+        if (prop) {
+          report(context, messages.requiredHasDefault, 'requiredHasDefault', {
+            node: defaultProp.node,
+            data: {
+              name: defaultPropName,
+            },
+          });
+        } else {
+          report(context, messages.defaultHasNoType, 'defaultHasNoType', {
+            node: defaultProp.node,
+            data: {
+              name: defaultPropName,
+            },
+          });
+        }
+      });
+    }
+
+    // --------------------------------------------------------------------------
+    // Public API
+    // --------------------------------------------------------------------------
+
+    return {
+      'Program:exit'() {
+        // If no defaultProps could be found, we don't report anything.
+        values(components.list())
+          .filter((component) => component.defaultProps)
+          .forEach((component) => {
+            reportInvalidDefaultProps(
+              component.declaredPropTypes,
+              component.defaultProps || {}
+            );
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=destructuring-assignment.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"destructuring-assignment.d.ts","sourceRoot":"","sources":["destructuring-assignment.js"],"names":[],"mappings":"wBA2DW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,319 @@
+/**
+ * @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context.
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
+const report = require('../util/report');
+
+const getScope = eslintUtil.getScope;
+const getText = eslintUtil.getText;
+
+const DEFAULT_OPTION = 'always';
+
+function createSFCParams() {
+  const queue = [];
+
+  return {
+    push(params) {
+      queue.unshift(params);
+    },
+    pop() {
+      queue.shift();
+    },
+    propsName() {
+      const found = queue.find((params) => {
+        const props = params[0];
+        return props && !props.destructuring && props.name;
+      });
+      return found && found[0] && found[0].name;
+    },
+    contextName() {
+      const found = queue.find((params) => {
+        const context = params[1];
+        return context && !context.destructuring && context.name;
+      });
+      return found && found[1] && found[1].name;
+    },
+  };
+}
+
+function evalParams(params) {
+  return params.map((param) => ({
+    destructuring: param.type === 'ObjectPattern',
+    name: param.type === 'Identifier' && param.name,
+  }));
+}
+
+const messages = {
+  noDestructPropsInSFCArg: 'Must never use destructuring props assignment in SFC argument',
+  noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument',
+  noDestructAssignment: 'Must never use destructuring {{type}} assignment',
+  useDestructAssignment: 'Must use destructuring {{type}} assignment',
+  destructureInSignature: 'Must destructure props in the function signature.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce consistent usage of destructuring assignment of props, state, and context',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('destructuring-assignment'),
+    },
+    fixable: 'code',
+    messages,
+
+    schema: [{
+      type: 'string',
+      enum: [
+        'always',
+        'never',
+      ],
+    }, {
+      type: 'object',
+      properties: {
+        ignoreClassFields: {
+          type: 'boolean',
+        },
+        destructureInSignature: {
+          type: 'string',
+          enum: [
+            'always',
+            'ignore',
+          ],
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const configuration = context.options[0] || DEFAULT_OPTION;
+    const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
+    const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore';
+    const sfcParams = createSFCParams();
+
+    /**
+     * @param {ASTNode} node We expect either an ArrowFunctionExpression,
+     *   FunctionDeclaration, or FunctionExpression
+     */
+    function handleStatelessComponent(node) {
+      const params = evalParams(node.params);
+
+      const SFCComponent = components.get(getScope(context, node).block);
+      if (!SFCComponent) {
+        return;
+      }
+      sfcParams.push(params);
+
+      if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') {
+        report(context, messages.noDestructPropsInSFCArg, 'noDestructPropsInSFCArg', {
+          node,
+        });
+      } else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') {
+        report(context, messages.noDestructContextInSFCArg, 'noDestructContextInSFCArg', {
+          node,
+        });
+      }
+    }
+
+    function handleStatelessComponentExit(node) {
+      const SFCComponent = components.get(getScope(context, node).block);
+      if (SFCComponent) {
+        sfcParams.pop();
+      }
+    }
+
+    function handleSFCUsage(node) {
+      const propsName = sfcParams.propsName();
+      const contextName = sfcParams.contextName();
+      // props.aProp || context.aProp
+      const isPropUsed = (
+        (propsName && node.object.name === propsName)
+          || (contextName && node.object.name === contextName)
+      )
+        && !isAssignmentLHS(node);
+      if (isPropUsed && configuration === 'always' && !node.optional) {
+        report(context, messages.useDestructAssignment, 'useDestructAssignment', {
+          node,
+          data: {
+            type: node.object.name,
+          },
+        });
+      }
+    }
+
+    function isInClassProperty(node) {
+      let curNode = node.parent;
+      while (curNode) {
+        if (curNode.type === 'ClassProperty' || curNode.type === 'PropertyDefinition') {
+          return true;
+        }
+        curNode = curNode.parent;
+      }
+      return false;
+    }
+
+    function handleClassUsage(node) {
+      // this.props.Aprop || this.context.aProp || this.state.aState
+      const isPropUsed = (
+        node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression'
+        && (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state')
+        && !isAssignmentLHS(node)
+      );
+
+      if (
+        isPropUsed && configuration === 'always'
+        && !(ignoreClassFields && isInClassProperty(node))
+      ) {
+        report(context, messages.useDestructAssignment, 'useDestructAssignment', {
+          node,
+          data: {
+            type: node.object.property.name,
+          },
+        });
+      }
+    }
+
+    // valid-jsdoc cannot read function types
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * Find a parent that satisfy the given predicate
+     * @param {ASTNode} node
+     * @param {(node: ASTNode) => boolean} predicate
+     * @returns {ASTNode | undefined}
+     */
+    function findParent(node, predicate) {
+      let n = node;
+      while (n) {
+        if (predicate(n)) {
+          return n;
+        }
+        n = n.parent;
+      }
+      return undefined;
+    }
+
+    return {
+
+      FunctionDeclaration: handleStatelessComponent,
+
+      ArrowFunctionExpression: handleStatelessComponent,
+
+      FunctionExpression: handleStatelessComponent,
+
+      'FunctionDeclaration:exit': handleStatelessComponentExit,
+
+      'ArrowFunctionExpression:exit': handleStatelessComponentExit,
+
+      'FunctionExpression:exit': handleStatelessComponentExit,
+
+      MemberExpression(node) {
+        const SFCComponent = utils.getParentStatelessComponent(node);
+        if (SFCComponent) {
+          handleSFCUsage(node);
+        }
+
+        const classComponent = utils.getParentComponent(node);
+        if (classComponent) {
+          handleClassUsage(node);
+        }
+      },
+
+      TSQualifiedName(node) {
+        if (configuration !== 'always') {
+          return;
+        }
+        // handle `typeof props.a.b`
+        if (node.left.type === 'Identifier'
+          && node.left.name === sfcParams.propsName()
+          && findParent(node, (n) => n.type === 'TSTypeQuery')
+          && utils.getParentStatelessComponent(node)
+        ) {
+          report(context, messages.useDestructAssignment, 'useDestructAssignment', {
+            node,
+            data: {
+              type: 'props',
+            },
+          });
+        }
+      },
+
+      VariableDeclarator(node) {
+        const classComponent = utils.getParentComponent(node);
+        const SFCComponent = components.get(getScope(context, node).block);
+
+        const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
+        // let {foo} = props;
+        const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
+        // let {foo} = this.props;
+        const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
+          node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
+        );
+
+        if (SFCComponent && destructuringSFC && configuration === 'never') {
+          report(context, messages.noDestructAssignment, 'noDestructAssignment', {
+            node,
+            data: {
+              type: node.init.name,
+            },
+          });
+        }
+
+        if (
+          classComponent && destructuringClass && configuration === 'never'
+          && !(ignoreClassFields && (node.parent.type === 'ClassProperty' || node.parent.type === 'PropertyDefinition'))
+        ) {
+          report(context, messages.noDestructAssignment, 'noDestructAssignment', {
+            node,
+            data: {
+              type: node.init.property.name,
+            },
+          });
+        }
+
+        if (
+          SFCComponent
+          && destructuringSFC
+          && configuration === 'always'
+          && destructureInSignature === 'always'
+          && node.init.name === 'props'
+        ) {
+          const scopeSetProps = getScope(context, node).set.get('props');
+          const propsRefs = scopeSetProps && scopeSetProps.references;
+          if (!propsRefs) {
+            return;
+          }
+
+          // Skip if props is used elsewhere
+          if (propsRefs.length > 1) {
+            return;
+          }
+          report(context, messages.destructureInSignature, 'destructureInSignature', {
+            node,
+            fix(fixer) {
+              const param = SFCComponent.node.params[0];
+              if (!param) {
+                return;
+              }
+              const replaceRange = [
+                param.range[0],
+                param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
+              ];
+              return [
+                fixer.replaceTextRange(replaceRange, getText(context, node.id)),
+                fixer.remove(node.parent),
+              ];
+            },
+          });
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=display-name.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/display-name.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"display-name.d.ts","sourceRoot":"","sources":["display-name.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/display-name.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/display-name.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/display-name.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,285 @@
+/**
+ * @fileoverview Prevent missing displayName in a React component definition
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const values = require('object.values');
+const filter = require('es-iterator-helpers/Iterator.prototype.filter');
+const forEach = require('es-iterator-helpers/Iterator.prototype.forEach');
+
+const Components = require('../util/Components');
+const isCreateContext = require('../util/isCreateContext');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const testReactVersion = require('../util/version').testReactVersion;
+const propsUtil = require('../util/props');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noDisplayName: 'Component definition is missing display name',
+  noContextDisplayName: 'Context definition is missing display name',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow missing displayName in a React component definition',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('display-name'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreTranspilerName: {
+          type: 'boolean',
+        },
+        checkContextObjects: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const config = context.options[0] || {};
+    const ignoreTranspilerName = config.ignoreTranspilerName || false;
+    const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0');
+
+    const contextObjects = new Map();
+
+    /**
+     * Mark a prop type as declared
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markDisplayNameAsDeclared(node) {
+      components.set(node, {
+        hasDisplayName: true,
+      });
+    }
+
+    /**
+     * Checks if React.forwardRef is nested inside React.memo
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if React.forwardRef is nested inside React.memo, false if not.
+     */
+    function isNestedMemo(node) {
+      return astUtil.isCallExpression(node)
+        && node.arguments
+        && astUtil.isCallExpression(node.arguments[0])
+        && utils.isPragmaComponentWrapper(node);
+    }
+
+    /**
+     * Reports missing display name for a given component
+     * @param {Object} component The component to process
+     */
+    function reportMissingDisplayName(component) {
+      if (
+        testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0')
+        && isNestedMemo(component.node)
+      ) {
+        return;
+      }
+
+      report(context, messages.noDisplayName, 'noDisplayName', {
+        node: component.node,
+      });
+    }
+
+    /**
+     * Reports missing display name for a given context object
+     * @param {Object} contextObj The context object to process
+     */
+    function reportMissingContextDisplayName(contextObj) {
+      report(context, messages.noContextDisplayName, 'noContextDisplayName', {
+        node: contextObj.node,
+      });
+    }
+
+    /**
+     * Checks if the component have a name set by the transpiler
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if component has a name, false if not.
+     */
+    function hasTranspilerName(node) {
+      const namedObjectAssignment = (
+        node.type === 'ObjectExpression'
+        && node.parent
+        && node.parent.parent
+        && node.parent.parent.type === 'AssignmentExpression'
+        && (
+          !node.parent.parent.left.object
+          || node.parent.parent.left.object.name !== 'module'
+          || node.parent.parent.left.property.name !== 'exports'
+        )
+      );
+      const namedObjectDeclaration = (
+        node.type === 'ObjectExpression'
+        && node.parent
+        && node.parent.parent
+        && node.parent.parent.type === 'VariableDeclarator'
+      );
+      const namedClass = (
+        (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')
+        && node.id
+        && !!node.id.name
+      );
+
+      const namedFunctionDeclaration = (
+        (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')
+        && node.id
+        && !!node.id.name
+      );
+
+      const namedFunctionExpression = (
+        astUtil.isFunctionLikeExpression(node)
+        && node.parent
+        && (node.parent.type === 'VariableDeclarator' || node.parent.type === 'Property' || node.parent.method === true)
+        && (!node.parent.parent || !componentUtil.isES5Component(node.parent.parent, context))
+      );
+
+      if (
+        namedObjectAssignment || namedObjectDeclaration
+        || namedClass
+        || namedFunctionDeclaration || namedFunctionExpression
+      ) {
+        return true;
+      }
+      return false;
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      ExpressionStatement(node) {
+        if (checkContextObjects && isCreateContext(node)) {
+          contextObjects.set(node.expression.left.name, { node, hasDisplayName: false });
+        }
+      },
+      VariableDeclarator(node) {
+        if (checkContextObjects && isCreateContext(node)) {
+          contextObjects.set(node.id.name, { node, hasDisplayName: false });
+        }
+      },
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!propsUtil.isDisplayNameDeclaration(node)) {
+          return;
+        }
+        markDisplayNameAsDeclared(node);
+      },
+
+      MemberExpression(node) {
+        if (!propsUtil.isDisplayNameDeclaration(node.property)) {
+          return;
+        }
+        if (
+          checkContextObjects
+          && node.object
+          && node.object.name
+          && contextObjects.has(node.object.name)
+        ) {
+          contextObjects.get(node.object.name).hasDisplayName = true;
+        }
+        const component = utils.getRelatedComponent(node);
+        if (!component) {
+          return;
+        }
+        markDisplayNameAsDeclared(astUtil.unwrapTSAsExpression(component.node));
+      },
+
+      'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) {
+        if (ignoreTranspilerName || !hasTranspilerName(node)) {
+          return;
+        }
+        if (components.get(node)) {
+          markDisplayNameAsDeclared(node);
+        }
+      },
+
+      MethodDefinition(node) {
+        if (!propsUtil.isDisplayNameDeclaration(node.key)) {
+          return;
+        }
+        markDisplayNameAsDeclared(node);
+      },
+
+      'ClassExpression, ClassDeclaration'(node) {
+        if (ignoreTranspilerName || !hasTranspilerName(node)) {
+          return;
+        }
+        markDisplayNameAsDeclared(node);
+      },
+
+      ObjectExpression(node) {
+        if (!componentUtil.isES5Component(node, context)) {
+          return;
+        }
+        if (ignoreTranspilerName || !hasTranspilerName(node)) {
+          // Search for the displayName declaration
+          node.properties.forEach((property) => {
+            if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) {
+              return;
+            }
+            markDisplayNameAsDeclared(node);
+          });
+          return;
+        }
+        markDisplayNameAsDeclared(node);
+      },
+
+      CallExpression(node) {
+        if (!utils.isPragmaComponentWrapper(node)) {
+          return;
+        }
+
+        if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
+          // Skip over React.forwardRef declarations that are embedded within
+          // a React.memo i.e. React.memo(React.forwardRef(/* ... */))
+          // This means that we raise a single error for the call to React.memo
+          // instead of one for React.memo and one for React.forwardRef
+          const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node);
+          if (
+            !isWrappedInAnotherPragma
+            && (ignoreTranspilerName || !hasTranspilerName(node.arguments[0]))
+          ) {
+            return;
+          }
+
+          if (components.get(node)) {
+            markDisplayNameAsDeclared(node);
+          }
+        }
+      },
+
+      'Program:exit'() {
+        const list = components.list();
+        // Report missing display name for all components
+        values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
+          reportMissingDisplayName(component);
+        });
+        if (checkContextObjects) {
+          // Report missing display name for all context objects
+          forEach(
+            filter(contextObjects.values(), (v) => !v.hasDisplayName),
+            (contextObj) => reportMissingContextDisplayName(contextObj)
+          );
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forbid-component-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forbid-component-props.d.ts","sourceRoot":"","sources":["forbid-component-props.js"],"names":[],"mappings":"wBAyBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-component-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,239 @@
+/**
+ * @fileoverview Forbid certain props on components
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+const minimatch = require('minimatch');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = ['className', 'style'];
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  propIsForbidden: 'Prop "{{prop}}" is forbidden on Components',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow certain props on components',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('forbid-component-props'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            anyOf: [
+              { type: 'string' },
+              {
+                type: 'object',
+                properties: {
+                  propName: { type: 'string' },
+                  allowedFor: {
+                    type: 'array',
+                    uniqueItems: true,
+                    items: { type: 'string' },
+                  },
+                  allowedForPatterns: {
+                    type: 'array',
+                    uniqueItems: true,
+                    items: { type: 'string' },
+                  },
+                  message: { type: 'string' },
+                },
+                additionalProperties: false,
+              },
+              {
+                type: 'object',
+                properties: {
+                  propName: { type: 'string' },
+                  disallowedFor: {
+                    type: 'array',
+                    uniqueItems: true,
+                    minItems: 1,
+                    items: { type: 'string' },
+                  },
+                  disallowedForPatterns: {
+                    type: 'array',
+                    uniqueItems: true,
+                    minItems: 1,
+                    items: { type: 'string' },
+                  },
+                  message: { type: 'string' },
+                },
+                anyOf: [
+                  { required: ['disallowedFor'] },
+                  { required: ['disallowedForPatterns'] },
+                ],
+                additionalProperties: false,
+              },
+              {
+                type: 'object',
+                properties: {
+                  propNamePattern: { type: 'string' },
+                  allowedFor: {
+                    type: 'array',
+                    uniqueItems: true,
+                    items: { type: 'string' },
+                  },
+                  allowedForPatterns: {
+                    type: 'array',
+                    uniqueItems: true,
+                    items: { type: 'string' },
+                  },
+                  message: { type: 'string' },
+                },
+                additionalProperties: false,
+              },
+              {
+                type: 'object',
+                properties: {
+                  propNamePattern: { type: 'string' },
+                  disallowedFor: {
+                    type: 'array',
+                    uniqueItems: true,
+                    minItems: 1,
+                    items: { type: 'string' },
+                  },
+                  disallowedForPatterns: {
+                    type: 'array',
+                    uniqueItems: true,
+                    minItems: 1,
+                    items: { type: 'string' },
+                  },
+                  message: { type: 'string' },
+                },
+                anyOf: [
+                  { required: ['disallowedFor'] },
+                  { required: ['disallowedForPatterns'] },
+                ],
+                additionalProperties: false,
+              },
+            ],
+          },
+        },
+      },
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
+      const propName = typeof value === 'string' ? value : value.propName;
+      const propPattern = value.propNamePattern;
+      const prop = propName || propPattern;
+      const options = {
+        allowList: [].concat(value.allowedFor || []),
+        allowPatternList: [].concat(value.allowedForPatterns || []),
+        disallowList: [].concat(value.disallowedFor || []),
+        disallowPatternList: [].concat(value.disallowedForPatterns || []),
+        message: typeof value === 'string' ? null : value.message,
+        isPattern: !!value.propNamePattern,
+      };
+      return [prop, options];
+    }));
+
+    function getPropOptions(prop) {
+      // Get config options having pattern
+      const propNamePatternArray = Array.from(forbid.entries()).filter((propEntry) => propEntry[1].isPattern);
+      // Match current prop with pattern options, return if matched
+      const propNamePattern = propNamePatternArray.find((propPatternVal) => minimatch(prop, propPatternVal[0]));
+      // Get options for matched propNamePattern
+      const propNamePatternOptions = propNamePattern && propNamePattern[1];
+
+      const options = forbid.get(prop) || propNamePatternOptions;
+      return options;
+    }
+
+    function isForbidden(prop, tagName) {
+      const options = getPropOptions(prop);
+      if (!options) {
+        return false;
+      }
+
+      function checkIsTagForbiddenByAllowOptions() {
+        if (options.allowList.indexOf(tagName) !== -1) {
+          return false;
+        }
+
+        if (options.allowPatternList.length === 0) {
+          return true;
+        }
+
+        return options.allowPatternList.every(
+          (pattern) => !minimatch(tagName, pattern)
+        );
+      }
+
+      function checkIsTagForbiddenByDisallowOptions() {
+        if (options.disallowList.indexOf(tagName) !== -1) {
+          return true;
+        }
+
+        if (options.disallowPatternList.length === 0) {
+          return false;
+        }
+
+        return options.disallowPatternList.some(
+          (pattern) => minimatch(tagName, pattern)
+        );
+      }
+
+      const hasDisallowOptions = options.disallowList.length > 0 || options.disallowPatternList.length > 0;
+
+      // disallowList should have a least one item (schema configuration)
+      const isTagForbidden = hasDisallowOptions
+        ? checkIsTagForbiddenByDisallowOptions()
+        : checkIsTagForbiddenByAllowOptions();
+
+      // if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
+      return typeof tagName === 'undefined' || isTagForbidden;
+    }
+
+    return {
+      JSXAttribute(node) {
+        const parentName = node.parent.name;
+        // Extract a component name when using a "namespace", e.g. `<AntdLayout.Content />`.
+        const tag = parentName.name || `${parentName.object.name}.${parentName.property.name}`;
+        const componentName = parentName.name || parentName.property.name;
+        if (componentName && typeof componentName[0] === 'string' && componentName[0] !== componentName[0].toUpperCase()) {
+          // This is a DOM node, not a Component, so exit.
+          return;
+        }
+
+        const prop = node.name.name;
+
+        if (!isForbidden(prop, tag)) {
+          return;
+        }
+
+        const customMessage = getPropOptions(prop).message;
+
+        report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
+          node,
+          data: {
+            prop,
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forbid-dom-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forbid-dom-props.d.ts","sourceRoot":"","sources":["forbid-dom-props.js"],"names":[],"mappings":"wBAuCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-dom-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Forbid certain props on DOM Nodes
+ * @author David Vázquez
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = [];
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+/**
+ * @param {Map<string, object>} forbidMap // { disallowList: null | string[], message: null | string }
+ * @param {string} prop
+ * @param {string} tagName
+ * @returns {boolean}
+ */
+function isForbidden(forbidMap, prop, tagName) {
+  const options = forbidMap.get(prop);
+  return options && (
+    typeof tagName === 'undefined'
+    || !options.disallowList
+    || options.disallowList.indexOf(tagName) !== -1
+  );
+}
+
+const messages = {
+  propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow certain props on DOM Nodes',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('forbid-dom-props'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            anyOf: [{
+              type: 'string',
+            }, {
+              type: 'object',
+              properties: {
+                propName: {
+                  type: 'string',
+                },
+                disallowedFor: {
+                  type: 'array',
+                  uniqueItems: true,
+                  items: {
+                    type: 'string',
+                  },
+                },
+                message: {
+                  type: 'string',
+                },
+              },
+            }],
+            minLength: 1,
+          },
+          uniqueItems: true,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
+      const propName = typeof value === 'string' ? value : value.propName;
+      return [propName, {
+        disallowList: typeof value === 'string' ? null : (value.disallowedFor || null),
+        message: typeof value === 'string' ? null : value.message,
+      }];
+    }));
+
+    return {
+      JSXAttribute(node) {
+        const tag = node.parent.name.name;
+        if (!(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())) {
+          // This is a Component, not a DOM node, so exit.
+          return;
+        }
+
+        const prop = node.name.name;
+
+        if (!isForbidden(forbid, prop, tag)) {
+          return;
+        }
+
+        const customMessage = forbid.get(prop).message;
+
+        report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
+          node,
+          data: {
+            prop,
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forbid-elements.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forbid-elements.d.ts","sourceRoot":"","sources":["forbid-elements.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-elements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,119 @@
+/**
+ * @fileoverview Forbid certain elements
+ * @author Kenneth Chung
+ */
+
+'use strict';
+
+const has = require('hasown');
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  forbiddenElement: '<{{element}}> is forbidden',
+  forbiddenElement_message: '<{{element}}> is forbidden, {{message}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow certain elements',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('forbid-elements'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            anyOf: [
+              { type: 'string' },
+              {
+                type: 'object',
+                properties: {
+                  element: { type: 'string' },
+                  message: { type: 'string' },
+                },
+                required: ['element'],
+                additionalProperties: false,
+              },
+            ],
+          },
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const forbidConfiguration = configuration.forbid || [];
+
+    /** @type {Record<string, { element: string, message?: string }>} */
+    const indexedForbidConfigs = {};
+
+    forbidConfiguration.forEach((item) => {
+      if (typeof item === 'string') {
+        indexedForbidConfigs[item] = { element: item };
+      } else {
+        indexedForbidConfigs[item.element] = item;
+      }
+    });
+
+    function reportIfForbidden(element, node) {
+      if (has(indexedForbidConfigs, element)) {
+        const message = indexedForbidConfigs[element].message;
+
+        report(
+          context,
+          message ? messages.forbiddenElement_message : messages.forbiddenElement,
+          message ? 'forbiddenElement_message' : 'forbiddenElement',
+          {
+            node,
+            data: {
+              element,
+              message,
+            },
+          }
+        );
+      }
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        reportIfForbidden(getText(context, node.name), node.name);
+      },
+
+      CallExpression(node) {
+        if (!isCreateElement(context, node)) {
+          return;
+        }
+
+        const argument = node.arguments[0];
+        if (!argument) {
+          return;
+        }
+
+        if (argument.type === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
+          reportIfForbidden(argument.name, argument);
+        } else if (argument.type === 'Literal' && /^[a-z][^.]*$/.test(String(argument.value))) {
+          reportIfForbidden(argument.value, argument);
+        } else if (argument.type === 'MemberExpression') {
+          reportIfForbidden(getText(context, argument), argument);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forbid-foreign-prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forbid-foreign-prop-types.d.ts","sourceRoot":"","sources":["forbid-foreign-prop-types.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,141 @@
+/**
+ * @fileoverview Forbid using another component's propTypes
+ * @author Ian Christian Myers
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const ast = require('../util/ast');
+const report = require('../util/report');
+
+const messages = {
+  forbiddenPropType: 'Using propTypes from another component is not safe because they may be removed in production builds',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow using another component\'s propTypes',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('forbid-foreign-prop-types'),
+    },
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allowInPropTypes: {
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
+  },
+
+  create(context) {
+    const config = context.options[0] || {};
+    const allowInPropTypes = config.allowInPropTypes || false;
+
+    // --------------------------------------------------------------------------
+    // Helpers
+    // --------------------------------------------------------------------------
+
+    function findParentAssignmentExpression(node) {
+      let parent = node.parent;
+
+      while (parent && parent.type !== 'Program') {
+        if (parent.type === 'AssignmentExpression') {
+          return parent;
+        }
+        parent = parent.parent;
+      }
+      return null;
+    }
+
+    function findParentClassProperty(node) {
+      let parent = node.parent;
+
+      while (parent && parent.type !== 'Program') {
+        if (parent.type === 'ClassProperty' || parent.type === 'PropertyDefinition') {
+          return parent;
+        }
+        parent = parent.parent;
+      }
+      return null;
+    }
+
+    function isAllowedAssignment(node) {
+      if (!allowInPropTypes) {
+        return false;
+      }
+
+      const assignmentExpression = findParentAssignmentExpression(node);
+
+      if (
+        assignmentExpression
+        && assignmentExpression.left
+        && assignmentExpression.left.property
+        && assignmentExpression.left.property.name === 'propTypes'
+      ) {
+        return true;
+      }
+
+      const classProperty = findParentClassProperty(node);
+
+      if (
+        classProperty
+        && classProperty.key
+        && classProperty.key.name === 'propTypes'
+      ) {
+        return true;
+      }
+      return false;
+    }
+
+    return {
+      MemberExpression(node) {
+        if (
+          (node.property
+          && (
+            !node.computed
+            && node.property.type === 'Identifier'
+            && node.property.name === 'propTypes'
+            && !ast.isAssignmentLHS(node)
+            && !isAllowedAssignment(node)
+          )) || (
+            // @ts-expect-error: The JSXText type is not present in the estree type definitions
+            (node.property.type === 'Literal' || node.property.type === 'JSXText')
+            && 'value' in node.property
+            && node.property.value === 'propTypes'
+            && !ast.isAssignmentLHS(node)
+            && !isAllowedAssignment(node)
+          )
+        ) {
+          report(context, messages.forbiddenPropType, 'forbiddenPropType', {
+            node: node.property,
+          });
+        }
+      },
+
+      ObjectPattern(node) {
+        const propTypesNode = node.properties.find((property) => (
+          property.type === 'Property'
+          && 'name' in property.key
+          && property.key.name === 'propTypes'
+        ));
+
+        if (propTypesNode) {
+          report(context, messages.forbiddenPropType, 'forbiddenPropType', {
+            node: propTypesNode,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forbid-prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forbid-prop-types.d.ts","sourceRoot":"","sources":["forbid-prop-types.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forbid-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,298 @@
+/**
+ * @fileoverview Forbid certain propTypes
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const propsUtil = require('../util/props');
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = ['any', 'array', 'object'];
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  forbiddenPropType: 'Prop type "{{target}}" is forbidden',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow certain propTypes',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('forbid-prop-types'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        checkContextTypes: {
+          type: 'boolean',
+        },
+        checkChildContextTypes: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: true,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const checkContextTypes = configuration.checkContextTypes || false;
+    const checkChildContextTypes = configuration.checkChildContextTypes || false;
+    let propTypesPackageName = null;
+    let reactPackageName = null;
+    let isForeignPropTypesPackage = false;
+
+    function isPropTypesPackage(node) {
+      return (
+        node.type === 'Identifier'
+        && (
+          node.name === null
+          || node.name === propTypesPackageName
+          || !isForeignPropTypesPackage
+        )
+      ) || (
+        node.type === 'MemberExpression'
+        && (
+          node.object.name === null
+          || node.object.name === reactPackageName
+          || !isForeignPropTypesPackage
+        )
+      );
+    }
+
+    function isForbidden(type) {
+      const forbid = configuration.forbid || DEFAULTS;
+      return forbid.indexOf(type) >= 0;
+    }
+
+    function reportIfForbidden(type, declaration, target) {
+      if (isForbidden(type)) {
+        report(context, messages.forbiddenPropType, 'forbiddenPropType', {
+          node: declaration,
+          data: {
+            target,
+          },
+        });
+      }
+    }
+
+    function shouldCheckContextTypes(node) {
+      if (checkContextTypes && propsUtil.isContextTypesDeclaration(node)) {
+        return true;
+      }
+      return false;
+    }
+
+    function shouldCheckChildContextTypes(node) {
+      if (checkChildContextTypes && propsUtil.isChildContextTypesDeclaration(node)) {
+        return true;
+      }
+      return false;
+    }
+
+    /**
+     * Checks if propTypes declarations are forbidden
+     * @param {Array} declarations The array of AST nodes being checked.
+     * @returns {void}
+     */
+    function checkProperties(declarations) {
+      if (declarations) {
+        declarations.forEach((declaration) => {
+          if (declaration.type !== 'Property') {
+            return;
+          }
+          let target;
+          let value = declaration.value;
+          if (
+            value.type === 'MemberExpression'
+            && value.property
+            && value.property.name
+            && value.property.name === 'isRequired'
+          ) {
+            value = value.object;
+          }
+          if (astUtil.isCallExpression(value)) {
+            if (!isPropTypesPackage(value.callee)) {
+              return;
+            }
+            value.arguments.forEach((arg) => {
+              const name = arg.type === 'MemberExpression' ? arg.property.name : arg.name;
+              reportIfForbidden(name, declaration, name);
+            });
+            value = value.callee;
+          }
+          if (!isPropTypesPackage(value)) {
+            return;
+          }
+          if (value.property) {
+            target = value.property.name;
+          } else if (value.type === 'Identifier') {
+            target = value.name;
+          }
+          reportIfForbidden(target, declaration, target);
+        });
+      }
+    }
+
+    function checkNode(node) {
+      if (!node) {
+        return;
+      }
+
+      if (node.type === 'ObjectExpression') {
+        checkProperties(node.properties);
+      } else if (node.type === 'Identifier') {
+        const propTypesObject = variableUtil.findVariableByName(context, node, node.name);
+        if (propTypesObject && propTypesObject.properties) {
+          checkProperties(propTypesObject.properties);
+        }
+      } else if (astUtil.isCallExpression(node)) {
+        const innerNode = node.arguments && node.arguments[0];
+        if (
+          propWrapperUtil.isPropWrapperFunction(context, getText(context, node.callee))
+            && innerNode
+        ) {
+          checkNode(innerNode);
+        }
+      }
+    }
+
+    return {
+      ImportDeclaration(node) {
+        if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types"
+          if (node.specifiers.length > 0) {
+            propTypesPackageName = node.specifiers[0].local.name;
+          }
+        } else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
+          if (node.specifiers.length > 0) {
+            reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"`
+          }
+          if (node.specifiers.length >= 1) {
+            const propTypesSpecifier = node.specifiers.find((specifier) => (
+              'imported' in specifier
+              && specifier.imported
+              && 'name' in specifier.imported
+              && specifier.imported.name === 'PropTypes'
+            ));
+            if (propTypesSpecifier) {
+              propTypesPackageName = propTypesSpecifier.local.name;
+            }
+          }
+        } else { // package is not imported from "react" or "prop-types"
+          // eslint-disable-next-line no-lonely-if
+          if (node.specifiers.some((x) => x.local.name === 'PropTypes')) { // assert: node.specifiers.length > 1
+            isForeignPropTypesPackage = true;
+          }
+        }
+      },
+
+      'ClassProperty, PropertyDefinition'(node) {
+        if (
+          !propsUtil.isPropTypesDeclaration(node)
+          && !isPropTypesPackage(node)
+          && !shouldCheckContextTypes(node)
+          && !shouldCheckChildContextTypes(node)
+        ) {
+          return;
+        }
+        checkNode(node.value);
+      },
+
+      MemberExpression(node) {
+        if (
+          !propsUtil.isPropTypesDeclaration(node)
+          && !isPropTypesPackage(node)
+          && !shouldCheckContextTypes(node)
+          && !shouldCheckChildContextTypes(node)
+        ) {
+          return;
+        }
+
+        checkNode('right' in node.parent && node.parent.right);
+      },
+
+      CallExpression(node) {
+        if (
+          node.callee.type === 'MemberExpression'
+          && node.callee.object
+          && !isPropTypesPackage(node.callee.object)
+          && !propsUtil.isPropTypesDeclaration(node.callee)
+        ) {
+          return;
+        }
+
+        if (
+          node.arguments.length > 0
+          && (
+            ('name' in node.callee && node.callee.name === 'shape')
+            || astUtil.getPropertyName(node.callee) === 'shape'
+          )
+        ) {
+          checkProperties('properties' in node.arguments[0] && node.arguments[0].properties);
+        }
+      },
+
+      MethodDefinition(node) {
+        if (
+          !propsUtil.isPropTypesDeclaration(node)
+          && !isPropTypesPackage(node)
+          && !shouldCheckContextTypes(node)
+          && !shouldCheckChildContextTypes(node)
+        ) {
+          return;
+        }
+
+        const returnStatement = astUtil.findReturnStatement(node);
+
+        if (returnStatement && returnStatement.argument) {
+          checkNode(returnStatement.argument);
+        }
+      },
+
+      ObjectExpression(node) {
+        node.properties.forEach((property) => {
+          if (!('key' in property) || !property.key) {
+            return;
+          }
+
+          if (
+            !propsUtil.isPropTypesDeclaration(property)
+            && !isPropTypesPackage(property)
+            && !shouldCheckContextTypes(property)
+            && !shouldCheckChildContextTypes(property)
+          ) {
+            return;
+          }
+          if (property.value.type === 'ObjectExpression') {
+            checkProperties(property.value.properties);
+          }
+        });
+      },
+
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=forward-ref-uses-ref.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"forward-ref-uses-ref.d.ts","sourceRoot":"","sources":["forward-ref-uses-ref.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/forward-ref-uses-ref.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+/**
+ * @fileoverview Require all forwardRef components include a ref parameter
+ */
+
+'use strict';
+
+const isParenthesized = require('../util/ast').isParenthesized;
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getMessageData = require('../util/message');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+/**
+ * @param {ASTNode} node
+ * @returns {boolean} If the node represents the identifier `forwardRef`.
+ */
+function isForwardRefIdentifier(node) {
+  return node.type === 'Identifier' && node.name === 'forwardRef';
+}
+
+/**
+ * @param {ASTNode} node
+ * @returns {boolean} If the node represents a function call `forwardRef()` or `React.forwardRef()`.
+ */
+function isForwardRefCall(node) {
+  return (
+    node.type === 'CallExpression'
+    && (
+      isForwardRefIdentifier(node.callee)
+      || (node.callee.type === 'MemberExpression' && isForwardRefIdentifier(node.callee.property))
+    )
+  );
+}
+
+const messages = {
+  missingRefParameter: 'forwardRef is used with this component but no ref parameter is set',
+  addRefParameter: 'Add a ref parameter',
+  removeForwardRef: 'Remove forwardRef wrapper',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Require all forwardRef components include a ref parameter',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('forward-ref-uses-ref'),
+    },
+    messages,
+    schema: [],
+    type: 'suggestion',
+    hasSuggestions: true,
+  },
+
+  create(context) {
+    const sourceCode = context.getSourceCode();
+
+    return {
+      'FunctionExpression, ArrowFunctionExpression'(node) {
+        if (!isForwardRefCall(node.parent)) {
+          return;
+        }
+
+        if (node.params.length === 1) {
+          report(context, messages.missingRefParameter, 'missingRefParameter', {
+            node,
+            suggest: [
+              Object.assign(
+                getMessageData('addRefParameter', messages.addRefParameter),
+                {
+                  fix(fixer) {
+                    const param = node.params[0];
+                    // If using shorthand arrow function syntax, add parentheses around the new parameter pair
+                    const shouldAddParentheses = node.type === 'ArrowFunctionExpression' && !isParenthesized(context, param);
+                    return [].concat(
+                      shouldAddParentheses ? fixer.insertTextBefore(param, '(') : [],
+                      fixer.insertTextAfter(param, `, ref${shouldAddParentheses ? ')' : ''}`)
+                    );
+                  },
+                }
+              ),
+              Object.assign(
+                getMessageData('removeForwardRef', messages.removeForwardRef),
+                {
+                  fix(fixer) {
+                    return fixer.replaceText(node.parent, sourceCode.getText(node));
+                  },
+                }
+              ),
+            ],
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=function-component-definition.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"function-component-definition.d.ts","sourceRoot":"","sources":["function-component-definition.js"],"names":[],"mappings":"wBAqHW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/function-component-definition.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,285 @@
+/**
+ * @fileoverview Standardize the way function component get defined
+ * @author Stefan Wullems
+ */
+
+'use strict';
+
+const arrayIncludes = require('array-includes');
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const reportC = require('../util/report');
+const getText = require('../util/eslint').getText;
+const propsUtil = require('../util/props');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function buildFunction(template, parts) {
+  return Object.keys(parts).reduce(
+    (acc, key) => acc.replace(`{${key}}`, () => parts[key] || ''),
+    template
+  );
+}
+
+const NAMED_FUNCTION_TEMPLATES = {
+  'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}',
+  'arrow-function': '{varType} {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}',
+  'function-expression': '{varType} {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}',
+};
+
+const UNNAMED_FUNCTION_TEMPLATES = {
+  'function-expression': 'function{typeParams}({params}){returnType} {body}',
+  'arrow-function': '{typeParams}({params}){returnType} => {body}',
+};
+
+function hasOneUnconstrainedTypeParam(node) {
+  const nodeTypeArguments = propsUtil.getTypeArguments(node);
+
+  return nodeTypeArguments
+    && nodeTypeArguments.params
+    && nodeTypeArguments.params.length === 1
+    && !nodeTypeArguments.params[0].constraint;
+}
+
+function hasName(node) {
+  return (
+    node.type === 'FunctionDeclaration'
+    || node.parent.type === 'VariableDeclarator'
+  );
+}
+
+function getNodeText(prop, source) {
+  if (!prop) return null;
+  return source.slice(prop.range[0], prop.range[1]);
+}
+
+function getName(node) {
+  if (node.type === 'FunctionDeclaration') {
+    return node.id.name;
+  }
+
+  if (
+    node.type === 'ArrowFunctionExpression'
+    || node.type === 'FunctionExpression'
+  ) {
+    return hasName(node) && node.parent.id.name;
+  }
+}
+
+function getParams(node, source) {
+  if (node.params.length === 0) return null;
+  return source.slice(
+    node.params[0].range[0],
+    node.params[node.params.length - 1].range[1]
+  );
+}
+
+function getBody(node, source) {
+  const range = node.body.range;
+
+  if (node.body.type !== 'BlockStatement') {
+    return ['{', `  return ${source.slice(range[0], range[1])}`, '}'].join('\n');
+  }
+
+  return source.slice(range[0], range[1]);
+}
+
+function getTypeAnnotation(node, source) {
+  if (!hasName(node) || node.type === 'FunctionDeclaration') return;
+
+  if (
+    node.type === 'ArrowFunctionExpression'
+    || node.type === 'FunctionExpression'
+  ) {
+    return getNodeText(node.parent.id.typeAnnotation, source);
+  }
+}
+
+function isUnfixableBecauseOfExport(node) {
+  return (
+    node.type === 'FunctionDeclaration'
+    && node.parent
+    && node.parent.type === 'ExportDefaultDeclaration'
+  );
+}
+
+function isFunctionExpressionWithName(node) {
+  return node.type === 'FunctionExpression' && node.id && node.id.name;
+}
+
+const messages = {
+  'function-declaration': 'Function component is not a function declaration',
+  'function-expression': 'Function component is not a function expression',
+  'arrow-function': 'Function component is not an arrow function',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce a specific function type for function components',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('function-component-definition'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          namedComponents: {
+            anyOf: [
+              {
+                enum: [
+                  'function-declaration',
+                  'arrow-function',
+                  'function-expression',
+                ],
+              },
+              {
+                type: 'array',
+                items: {
+                  type: 'string',
+                  enum: [
+                    'function-declaration',
+                    'arrow-function',
+                    'function-expression',
+                  ],
+                },
+              },
+            ],
+          },
+          unnamedComponents: {
+            anyOf: [
+              { enum: ['arrow-function', 'function-expression'] },
+              {
+                type: 'array',
+                items: {
+                  type: 'string',
+                  enum: ['arrow-function', 'function-expression'],
+                },
+              },
+            ],
+          },
+        },
+      },
+    ],
+  },
+
+  create: Components.detect((context, components) => {
+    const configuration = context.options[0] || {};
+    let fileVarType = 'var';
+
+    const namedConfig = [].concat(
+      configuration.namedComponents || 'function-declaration'
+    );
+    const unnamedConfig = [].concat(
+      configuration.unnamedComponents || 'function-expression'
+    );
+
+    function getFixer(node, options) {
+      const source = getText(context);
+
+      const typeAnnotation = getTypeAnnotation(node, source);
+
+      if (options.type === 'function-declaration' && typeAnnotation) {
+        return;
+      }
+      if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) {
+        return;
+      }
+      if (isUnfixableBecauseOfExport(node)) return;
+      if (isFunctionExpressionWithName(node)) return;
+      let varType = fileVarType;
+      if (
+        (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression')
+        && node.parent.type === 'VariableDeclarator'
+      ) {
+        varType = node.parent.parent.kind;
+      }
+
+      const nodeTypeArguments = propsUtil.getTypeArguments(node);
+      return (fixer) => fixer.replaceTextRange(
+        options.range,
+        buildFunction(options.template, {
+          typeAnnotation,
+          typeParams: getNodeText(nodeTypeArguments, source),
+          params: getParams(node, source),
+          returnType: getNodeText(node.returnType, source),
+          body: getBody(node, source),
+          name: getName(node),
+          varType,
+        })
+      );
+    }
+
+    function report(node, options) {
+      reportC(context, messages[options.messageId], options.messageId, {
+        node,
+        fix: getFixer(node, options.fixerOptions),
+      });
+    }
+
+    function validate(node, functionType) {
+      if (!components.get(node)) return;
+
+      if (node.parent && node.parent.type === 'Property') return;
+
+      if (hasName(node) && !arrayIncludes(namedConfig, functionType)) {
+        report(node, {
+          messageId: namedConfig[0],
+          fixerOptions: {
+            type: namedConfig[0],
+            template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]],
+            range:
+              node.type === 'FunctionDeclaration'
+                ? node.range
+                : node.parent.parent.range,
+          },
+        });
+      }
+      if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) {
+        report(node, {
+          messageId: unnamedConfig[0],
+          fixerOptions: {
+            type: unnamedConfig[0],
+            template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]],
+            range: node.range,
+          },
+        });
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+    const validatePairs = [];
+    let hasES6OrJsx = false;
+    return {
+      FunctionDeclaration(node) {
+        validatePairs.push([node, 'function-declaration']);
+      },
+      ArrowFunctionExpression(node) {
+        validatePairs.push([node, 'arrow-function']);
+      },
+      FunctionExpression(node) {
+        validatePairs.push([node, 'function-expression']);
+      },
+      VariableDeclaration(node) {
+        hasES6OrJsx = hasES6OrJsx || node.kind === 'const' || node.kind === 'let';
+      },
+      'Program:exit'() {
+        if (hasES6OrJsx) fileVarType = 'const';
+        validatePairs.forEach((pair) => validate(pair[0], pair[1]));
+      },
+      'ImportDeclaration, ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration, ExportSpecifier, ExportDefaultSpecifier, JSXElement, TSExportAssignment, TSImportEqualsDeclaration'() {
+        hasES6OrJsx = true;
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=hook-use-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"hook-use-state.d.ts","sourceRoot":"","sources":["hook-use-state.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,205 @@
+/**
+ * @fileoverview Ensure symmetric naming of useState hook value and setter variables
+ * @author Duncan Beevers
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getMessageData = require('../util/message');
+const getText = require('../util/eslint').getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function isNodeDestructuring(node) {
+  return node && (node.type === 'ArrayPattern' || node.type === 'ObjectPattern');
+}
+
+const messages = {
+  useStateErrorMessage: 'useState call is not destructured into value + setter pair',
+  useStateErrorMessageOrAddOption: 'useState call is not destructured into value + setter pair (you can allow destructuring by enabling "allowDestructuredState" option)',
+  suggestPair: 'Destructure useState call into value + setter pair',
+  suggestMemo: 'Replace useState call with useMemo',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Ensure destructuring and symmetric naming of useState hook value and setter variables',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('hook-use-state'),
+    },
+    messages,
+    schema: [{
+      type: 'object',
+      properties: {
+        allowDestructuredState: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+    type: 'suggestion',
+    hasSuggestions: true,
+  },
+
+  create: Components.detect((context, components, util) => {
+    const configuration = context.options[0] || {};
+    const allowDestructuredState = configuration.allowDestructuredState || false;
+
+    return {
+      CallExpression(node) {
+        const isImmediateReturn = node.parent
+          && node.parent.type === 'ReturnStatement';
+
+        if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) {
+          return;
+        }
+
+        const isDestructuringDeclarator = node.parent
+          && node.parent.type === 'VariableDeclarator'
+          && node.parent.id.type === 'ArrayPattern';
+
+        if (!isDestructuringDeclarator) {
+          report(
+            context,
+            messages.useStateErrorMessage,
+            'useStateErrorMessage',
+            {
+              node,
+              suggest: false,
+            }
+          );
+          return;
+        }
+
+        const variableNodes = node.parent.id.elements;
+        const valueVariable = variableNodes[0];
+        const setterVariable = variableNodes[1];
+        const isOnlyValueDestructuring = isNodeDestructuring(valueVariable) && !isNodeDestructuring(setterVariable);
+
+        if (allowDestructuredState && isOnlyValueDestructuring) {
+          return;
+        }
+
+        const valueVariableName = valueVariable
+          ? valueVariable.name
+          : undefined;
+
+        const setterVariableName = setterVariable
+          ? setterVariable.name
+          : undefined;
+
+        const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined;
+        const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined;
+        const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined;
+        const expectedSetterVariableNames = upperCaseCandidatePrefix ? [
+          `set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`,
+          `set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`,
+        ] : [];
+
+        const isSymmetricGetterSetterPair = valueVariable
+          && setterVariable
+          && expectedSetterVariableNames.indexOf(setterVariableName) !== -1
+          && variableNodes.length === 2;
+
+        if (!isSymmetricGetterSetterPair) {
+          const suggestions = [
+            Object.assign(
+              getMessageData('suggestPair', messages.suggestPair),
+              {
+                fix(fixer) {
+                  if (expectedSetterVariableNames.length > 0) {
+                    return fixer.replaceTextRange(
+                      node.parent.id.range,
+                      `[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
+                    );
+                  }
+                },
+              }
+            ),
+          ];
+
+          const defaultReactImports = components.getDefaultReactImports();
+          const defaultReactImportSpecifier = defaultReactImports
+            ? defaultReactImports[0]
+            : undefined;
+
+          const defaultReactImportName = defaultReactImportSpecifier
+            ? defaultReactImportSpecifier.local.name
+            : undefined;
+
+          const namedReactImports = components.getNamedReactImports();
+          const useStateReactImportSpecifier = namedReactImports
+            ? namedReactImports.find((specifier) => specifier.imported.name === 'useState')
+            : undefined;
+
+          const isSingleGetter = valueVariable && variableNodes.length === 1;
+          const isUseStateCalledWithSingleArgument = node.arguments.length === 1;
+          if (isSingleGetter && isUseStateCalledWithSingleArgument) {
+            const useMemoReactImportSpecifier = namedReactImports
+              && namedReactImports.find((specifier) => specifier.imported.name === 'useMemo');
+
+            let useMemoCode;
+            if (useMemoReactImportSpecifier) {
+              useMemoCode = useMemoReactImportSpecifier.local.name;
+            } else if (defaultReactImportName) {
+              useMemoCode = `${defaultReactImportName}.useMemo`;
+            } else {
+              useMemoCode = 'useMemo';
+            }
+
+            suggestions.unshift(Object.assign(
+              getMessageData('suggestMemo', messages.suggestMemo),
+              {
+                fix: (fixer) => [
+                  // Add useMemo import, if necessary
+                  useStateReactImportSpecifier
+                    && (!useMemoReactImportSpecifier || defaultReactImportName)
+                    && fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'),
+                  // Convert single-value destructure to simple assignment
+                  fixer.replaceTextRange(node.parent.id.range, valueVariableName),
+                  // Convert useState call to useMemo + arrow function + dependency array
+                  fixer.replaceTextRange(
+                    node.range,
+                    `${useMemoCode}(() => ${getText(context, node.arguments[0])}, [])`
+                  ),
+                ].filter(Boolean),
+              }
+            ));
+          }
+
+          if (isOnlyValueDestructuring) {
+            report(
+              context,
+              messages.useStateErrorMessageOrAddOption,
+              'useStateErrorMessageOrAddOption',
+              {
+                node: node.parent.id,
+                suggest: false,
+              }
+            );
+            return;
+          }
+
+          report(
+            context,
+            messages.useStateErrorMessage,
+            'useStateErrorMessage',
+            {
+              node: node.parent.id,
+              suggest: suggestions,
+            }
+          );
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=iframe-missing-sandbox.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"iframe-missing-sandbox.d.ts","sourceRoot":"","sources":["iframe-missing-sandbox.js"],"names":[],"mappings":"wBA+GW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/iframe-missing-sandbox.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,143 @@
+/**
+ * @fileoverview TBD
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+const messages = {
+  attributeMissing: 'An iframe element is missing a sandbox attribute',
+  invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"',
+  invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid',
+};
+
+const ALLOWED_VALUES = [
+  // From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
+  '',
+  'allow-downloads-without-user-activation',
+  'allow-downloads',
+  'allow-forms',
+  'allow-modals',
+  'allow-orientation-lock',
+  'allow-pointer-lock',
+  'allow-popups',
+  'allow-popups-to-escape-sandbox',
+  'allow-presentation',
+  'allow-same-origin',
+  'allow-scripts',
+  'allow-storage-access-by-user-activation',
+  'allow-top-navigation',
+  'allow-top-navigation-by-user-activation',
+];
+
+function validateSandboxAttribute(context, node, attribute) {
+  if (typeof attribute !== 'string') {
+    // Only string literals are supported for now
+    return;
+  }
+  const values = attribute.split(' ');
+  let allowScripts = false;
+  let allowSameOrigin = false;
+  values.forEach((attributeValue) => {
+    const trimmedAttributeValue = attributeValue.trim();
+    if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) {
+      report(context, messages.invalidValue, 'invalidValue', {
+        node,
+        data: {
+          value: trimmedAttributeValue,
+        },
+      });
+    }
+    if (trimmedAttributeValue === 'allow-scripts') {
+      allowScripts = true;
+    }
+    if (trimmedAttributeValue === 'allow-same-origin') {
+      allowSameOrigin = true;
+    }
+  });
+  if (allowScripts && allowSameOrigin) {
+    report(context, messages.invalidCombination, 'invalidCombination', {
+      node,
+    });
+  }
+}
+
+function checkAttributes(context, node) {
+  let sandboxAttributeFound = false;
+  node.attributes.forEach((attribute) => {
+    if (attribute.type === 'JSXAttribute'
+        && attribute.name
+        && attribute.name.type === 'JSXIdentifier'
+        && attribute.name.name === 'sandbox'
+    ) {
+      sandboxAttributeFound = true;
+      if (
+        attribute.value
+        && attribute.value.type === 'Literal'
+        && attribute.value.value
+      ) {
+        validateSandboxAttribute(context, node, attribute.value.value);
+      }
+    }
+  });
+  if (!sandboxAttributeFound) {
+    report(context, messages.attributeMissing, 'attributeMissing', {
+      node,
+    });
+  }
+}
+
+function checkProps(context, node) {
+  let sandboxAttributeFound = false;
+  if (node.arguments.length > 1) {
+    const props = node.arguments[1];
+    const sandboxProp = props.properties && props.properties.find((x) => x.type === 'Property' && x.key.name === 'sandbox');
+    if (sandboxProp) {
+      sandboxAttributeFound = true;
+      if (sandboxProp.value && sandboxProp.value.type === 'Literal' && sandboxProp.value.value) {
+        validateSandboxAttribute(context, node, sandboxProp.value.value);
+      }
+    }
+  }
+  if (!sandboxAttributeFound) {
+    report(context, messages.attributeMissing, 'attributeMissing', {
+      node,
+    });
+  }
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce sandbox attribute on iframe elements',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('iframe-missing-sandbox'),
+    },
+
+    schema: [],
+
+    messages,
+  },
+
+  create(context) {
+    return {
+      'JSXOpeningElement[name.name="iframe"]'(node) {
+        checkAttributes(context, node);
+      },
+
+      CallExpression(node) {
+        if (isCreateElement(context, node) && node.arguments && node.arguments.length > 0) {
+          const tag = node.arguments[0];
+          if (tag.type === 'Literal' && tag.value === 'iframe') {
+            checkProps(context, node);
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,108 @@
+export = rules;
+/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
+declare const rules: {
+    'boolean-prop-naming': import("eslint").Rule.RuleModule;
+    'button-has-type': import("eslint").Rule.RuleModule;
+    'checked-requires-onchange-or-readonly': import("eslint").Rule.RuleModule;
+    'default-props-match-prop-types': import("eslint").Rule.RuleModule;
+    'destructuring-assignment': import("eslint").Rule.RuleModule;
+    'display-name': import("eslint").Rule.RuleModule;
+    'forbid-component-props': import("eslint").Rule.RuleModule;
+    'forbid-dom-props': import("eslint").Rule.RuleModule;
+    'forbid-elements': import("eslint").Rule.RuleModule;
+    'forbid-foreign-prop-types': import("eslint").Rule.RuleModule;
+    'forbid-prop-types': import("eslint").Rule.RuleModule;
+    'forward-ref-uses-ref': import("eslint").Rule.RuleModule;
+    'function-component-definition': import("eslint").Rule.RuleModule;
+    'hook-use-state': import("eslint").Rule.RuleModule;
+    'iframe-missing-sandbox': import("eslint").Rule.RuleModule;
+    'jsx-boolean-value': import("eslint").Rule.RuleModule;
+    'jsx-child-element-spacing': import("eslint").Rule.RuleModule;
+    'jsx-closing-bracket-location': import("eslint").Rule.RuleModule;
+    'jsx-closing-tag-location': import("eslint").Rule.RuleModule;
+    'jsx-curly-spacing': import("eslint").Rule.RuleModule;
+    'jsx-curly-newline': import("eslint").Rule.RuleModule;
+    'jsx-equals-spacing': import("eslint").Rule.RuleModule;
+    'jsx-filename-extension': import("eslint").Rule.RuleModule;
+    'jsx-first-prop-new-line': import("eslint").Rule.RuleModule;
+    'jsx-handler-names': import("eslint").Rule.RuleModule;
+    'jsx-indent': import("eslint").Rule.RuleModule;
+    'jsx-indent-props': import("eslint").Rule.RuleModule;
+    'jsx-key': import("eslint").Rule.RuleModule;
+    'jsx-max-depth': import("eslint").Rule.RuleModule;
+    'jsx-max-props-per-line': import("eslint").Rule.RuleModule;
+    'jsx-newline': import("eslint").Rule.RuleModule;
+    'jsx-no-bind': import("eslint").Rule.RuleModule;
+    'jsx-no-comment-textnodes': import("eslint").Rule.RuleModule;
+    'jsx-no-constructed-context-values': import("eslint").Rule.RuleModule;
+    'jsx-no-duplicate-props': import("eslint").Rule.RuleModule;
+    'jsx-no-leaked-render': import("eslint").Rule.RuleModule;
+    'jsx-no-literals': import("eslint").Rule.RuleModule;
+    'jsx-no-script-url': import("eslint").Rule.RuleModule;
+    'jsx-no-target-blank': import("eslint").Rule.RuleModule;
+    'jsx-no-useless-fragment': import("eslint").Rule.RuleModule;
+    'jsx-one-expression-per-line': import("eslint").Rule.RuleModule;
+    'jsx-no-undef': import("eslint").Rule.RuleModule;
+    'jsx-curly-brace-presence': import("eslint").Rule.RuleModule;
+    'jsx-pascal-case': import("eslint").Rule.RuleModule;
+    'jsx-fragments': import("eslint").Rule.RuleModule;
+    'jsx-props-no-multi-spaces': import("eslint").Rule.RuleModule;
+    'jsx-props-no-spreading': import("eslint").Rule.RuleModule;
+    'jsx-props-no-spread-multi': import("eslint").Rule.RuleModule;
+    'jsx-sort-default-props': import("eslint").Rule.RuleModule;
+    'jsx-sort-props': import("eslint").Rule.RuleModule;
+    'jsx-space-before-closing': import("eslint").Rule.RuleModule;
+    'jsx-tag-spacing': import("eslint").Rule.RuleModule;
+    'jsx-uses-react': import("eslint").Rule.RuleModule;
+    'jsx-uses-vars': import("eslint").Rule.RuleModule;
+    'jsx-wrap-multilines': import("eslint").Rule.RuleModule;
+    'no-invalid-html-attribute': import("eslint").Rule.RuleModule;
+    'no-access-state-in-setstate': import("eslint").Rule.RuleModule;
+    'no-adjacent-inline-elements': import("eslint").Rule.RuleModule;
+    'no-array-index-key': import("eslint").Rule.RuleModule;
+    'no-arrow-function-lifecycle': import("eslint").Rule.RuleModule;
+    'no-children-prop': import("eslint").Rule.RuleModule;
+    'no-danger': import("eslint").Rule.RuleModule;
+    'no-danger-with-children': import("eslint").Rule.RuleModule;
+    'no-deprecated': import("eslint").Rule.RuleModule;
+    'no-did-mount-set-state': import("eslint").Rule.RuleModule;
+    'no-did-update-set-state': import("eslint").Rule.RuleModule;
+    'no-direct-mutation-state': import("eslint").Rule.RuleModule;
+    'no-find-dom-node': import("eslint").Rule.RuleModule;
+    'no-is-mounted': import("eslint").Rule.RuleModule;
+    'no-multi-comp': import("eslint").Rule.RuleModule;
+    'no-namespace': import("eslint").Rule.RuleModule;
+    'no-set-state': import("eslint").Rule.RuleModule;
+    'no-string-refs': import("eslint").Rule.RuleModule;
+    'no-redundant-should-component-update': import("eslint").Rule.RuleModule;
+    'no-render-return-value': import("eslint").Rule.RuleModule;
+    'no-this-in-sfc': import("eslint").Rule.RuleModule;
+    'no-typos': import("eslint").Rule.RuleModule;
+    'no-unescaped-entities': import("eslint").Rule.RuleModule;
+    'no-unknown-property': import("eslint").Rule.RuleModule;
+    'no-unsafe': import("eslint").Rule.RuleModule;
+    'no-unstable-nested-components': import("eslint").Rule.RuleModule;
+    'no-unused-class-component-methods': import("eslint").Rule.RuleModule;
+    'no-unused-prop-types': import("eslint").Rule.RuleModule;
+    'no-unused-state': import("eslint").Rule.RuleModule;
+    'no-object-type-as-default-prop': import("eslint").Rule.RuleModule;
+    'no-will-update-set-state': import("eslint").Rule.RuleModule;
+    'prefer-es6-class': import("eslint").Rule.RuleModule;
+    'prefer-exact-props': import("eslint").Rule.RuleModule;
+    'prefer-read-only-props': import("eslint").Rule.RuleModule;
+    'prefer-stateless-function': import("eslint").Rule.RuleModule;
+    'prop-types': import("eslint").Rule.RuleModule;
+    'react-in-jsx-scope': import("eslint").Rule.RuleModule;
+    'require-default-props': import("eslint").Rule.RuleModule;
+    'require-optimization': import("eslint").Rule.RuleModule;
+    'require-render-return': import("eslint").Rule.RuleModule;
+    'self-closing-comp': import("eslint").Rule.RuleModule;
+    'sort-comp': import("eslint").Rule.RuleModule;
+    'sort-default-props': import("eslint").Rule.RuleModule;
+    'sort-prop-types': import("eslint").Rule.RuleModule;
+    'state-in-constructor': import("eslint").Rule.RuleModule;
+    'static-property-placement': import("eslint").Rule.RuleModule;
+    'style-prop-object': import("eslint").Rule.RuleModule;
+    'void-dom-elements-no-children': import("eslint").Rule.RuleModule;
+};
+//# sourceMappingURL=index.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/index.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AAIA,oEAAoE;AACpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwGE"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/index.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,112 @@
+'use strict';
+
+/* eslint global-require: 0 */
+
+/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
+const rules = {
+  'boolean-prop-naming': require('./boolean-prop-naming'),
+  'button-has-type': require('./button-has-type'),
+  'checked-requires-onchange-or-readonly': require('./checked-requires-onchange-or-readonly'),
+  'default-props-match-prop-types': require('./default-props-match-prop-types'),
+  'destructuring-assignment': require('./destructuring-assignment'),
+  'display-name': require('./display-name'),
+  'forbid-component-props': require('./forbid-component-props'),
+  'forbid-dom-props': require('./forbid-dom-props'),
+  'forbid-elements': require('./forbid-elements'),
+  'forbid-foreign-prop-types': require('./forbid-foreign-prop-types'),
+  'forbid-prop-types': require('./forbid-prop-types'),
+  'forward-ref-uses-ref': require('./forward-ref-uses-ref'),
+  'function-component-definition': require('./function-component-definition'),
+  'hook-use-state': require('./hook-use-state'),
+  'iframe-missing-sandbox': require('./iframe-missing-sandbox'),
+  'jsx-boolean-value': require('./jsx-boolean-value'),
+  'jsx-child-element-spacing': require('./jsx-child-element-spacing'),
+  'jsx-closing-bracket-location': require('./jsx-closing-bracket-location'),
+  'jsx-closing-tag-location': require('./jsx-closing-tag-location'),
+  'jsx-curly-spacing': require('./jsx-curly-spacing'),
+  'jsx-curly-newline': require('./jsx-curly-newline'),
+  'jsx-equals-spacing': require('./jsx-equals-spacing'),
+  'jsx-filename-extension': require('./jsx-filename-extension'),
+  'jsx-first-prop-new-line': require('./jsx-first-prop-new-line'),
+  'jsx-handler-names': require('./jsx-handler-names'),
+  'jsx-indent': require('./jsx-indent'),
+  'jsx-indent-props': require('./jsx-indent-props'),
+  'jsx-key': require('./jsx-key'),
+  'jsx-max-depth': require('./jsx-max-depth'),
+  'jsx-max-props-per-line': require('./jsx-max-props-per-line'),
+  'jsx-newline': require('./jsx-newline'),
+  'jsx-no-bind': require('./jsx-no-bind'),
+  'jsx-no-comment-textnodes': require('./jsx-no-comment-textnodes'),
+  'jsx-no-constructed-context-values': require('./jsx-no-constructed-context-values'),
+  'jsx-no-duplicate-props': require('./jsx-no-duplicate-props'),
+  'jsx-no-leaked-render': require('./jsx-no-leaked-render'),
+  'jsx-no-literals': require('./jsx-no-literals'),
+  'jsx-no-script-url': require('./jsx-no-script-url'),
+  'jsx-no-target-blank': require('./jsx-no-target-blank'),
+  'jsx-no-useless-fragment': require('./jsx-no-useless-fragment'),
+  'jsx-one-expression-per-line': require('./jsx-one-expression-per-line'),
+  'jsx-no-undef': require('./jsx-no-undef'),
+  'jsx-curly-brace-presence': require('./jsx-curly-brace-presence'),
+  'jsx-pascal-case': require('./jsx-pascal-case'),
+  'jsx-fragments': require('./jsx-fragments'),
+  'jsx-props-no-multi-spaces': require('./jsx-props-no-multi-spaces'),
+  'jsx-props-no-spreading': require('./jsx-props-no-spreading'),
+  'jsx-props-no-spread-multi': require('./jsx-props-no-spread-multi'),
+  'jsx-sort-default-props': require('./jsx-sort-default-props'),
+  'jsx-sort-props': require('./jsx-sort-props'),
+  'jsx-space-before-closing': require('./jsx-space-before-closing'),
+  'jsx-tag-spacing': require('./jsx-tag-spacing'),
+  'jsx-uses-react': require('./jsx-uses-react'),
+  'jsx-uses-vars': require('./jsx-uses-vars'),
+  'jsx-wrap-multilines': require('./jsx-wrap-multilines'),
+  'no-invalid-html-attribute': require('./no-invalid-html-attribute'),
+  'no-access-state-in-setstate': require('./no-access-state-in-setstate'),
+  'no-adjacent-inline-elements': require('./no-adjacent-inline-elements'),
+  'no-array-index-key': require('./no-array-index-key'),
+  'no-arrow-function-lifecycle': require('./no-arrow-function-lifecycle'),
+  'no-children-prop': require('./no-children-prop'),
+  'no-danger': require('./no-danger'),
+  'no-danger-with-children': require('./no-danger-with-children'),
+  'no-deprecated': require('./no-deprecated'),
+  'no-did-mount-set-state': require('./no-did-mount-set-state'),
+  'no-did-update-set-state': require('./no-did-update-set-state'),
+  'no-direct-mutation-state': require('./no-direct-mutation-state'),
+  'no-find-dom-node': require('./no-find-dom-node'),
+  'no-is-mounted': require('./no-is-mounted'),
+  'no-multi-comp': require('./no-multi-comp'),
+  'no-namespace': require('./no-namespace'),
+  'no-set-state': require('./no-set-state'),
+  'no-string-refs': require('./no-string-refs'),
+  'no-redundant-should-component-update': require('./no-redundant-should-component-update'),
+  'no-render-return-value': require('./no-render-return-value'),
+  'no-this-in-sfc': require('./no-this-in-sfc'),
+  'no-typos': require('./no-typos'),
+  'no-unescaped-entities': require('./no-unescaped-entities'),
+  'no-unknown-property': require('./no-unknown-property'),
+  'no-unsafe': require('./no-unsafe'),
+  'no-unstable-nested-components': require('./no-unstable-nested-components'),
+  'no-unused-class-component-methods': require('./no-unused-class-component-methods'),
+  'no-unused-prop-types': require('./no-unused-prop-types'),
+  'no-unused-state': require('./no-unused-state'),
+  'no-object-type-as-default-prop': require('./no-object-type-as-default-prop'),
+  'no-will-update-set-state': require('./no-will-update-set-state'),
+  'prefer-es6-class': require('./prefer-es6-class'),
+  'prefer-exact-props': require('./prefer-exact-props'),
+  'prefer-read-only-props': require('./prefer-read-only-props'),
+  'prefer-stateless-function': require('./prefer-stateless-function'),
+  'prop-types': require('./prop-types'),
+  'react-in-jsx-scope': require('./react-in-jsx-scope'),
+  'require-default-props': require('./require-default-props'),
+  'require-optimization': require('./require-optimization'),
+  'require-render-return': require('./require-render-return'),
+  'self-closing-comp': require('./self-closing-comp'),
+  'sort-comp': require('./sort-comp'),
+  'sort-default-props': require('./sort-default-props'),
+  'sort-prop-types': require('./sort-prop-types'),
+  'state-in-constructor': require('./state-in-constructor'),
+  'static-property-placement': require('./static-property-placement'),
+  'style-prop-object': require('./style-prop-object'),
+  'void-dom-elements-no-children': require('./void-dom-elements-no-children'),
+};
+
+module.exports = rules;
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-boolean-value.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-boolean-value.d.ts","sourceRoot":"","sources":["jsx-boolean-value.js"],"names":[],"mappings":"wBAwDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-boolean-value.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,169 @@
+/**
+ * @fileoverview Enforce boolean attributes notation in JSX
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const exceptionsSchema = {
+  type: 'array',
+  items: { type: 'string', minLength: 1 },
+  uniqueItems: true,
+};
+
+const ALWAYS = 'always';
+const NEVER = 'never';
+
+/**
+ * @param {string} configuration
+ * @param {Set<string>} exceptions
+ * @param {string} propName
+ * @returns {boolean} propName
+ */
+function isAlways(configuration, exceptions, propName) {
+  const isException = exceptions.has(propName);
+  if (configuration === ALWAYS) {
+    return !isException;
+  }
+  return isException;
+}
+/**
+ * @param {string} configuration
+ * @param {Set<string>} exceptions
+ * @param {string} propName
+ * @returns {boolean} propName
+ */
+function isNever(configuration, exceptions, propName) {
+  const isException = exceptions.has(propName);
+  if (configuration === NEVER) {
+    return !isException;
+  }
+  return isException;
+}
+
+const messages = {
+  omitBoolean: 'Value must be omitted for boolean attribute `{{propName}}`',
+  setBoolean: 'Value must be set for boolean attribute `{{propName}}`',
+  omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce boolean attributes notation in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-boolean-value'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: {
+      anyOf: [{
+        type: 'array',
+        items: [{ enum: [ALWAYS, NEVER] }],
+        additionalItems: false,
+      }, {
+        type: 'array',
+        items: [{
+          enum: [ALWAYS],
+        }, {
+          type: 'object',
+          additionalProperties: false,
+          properties: {
+            [NEVER]: exceptionsSchema,
+            assumeUndefinedIsFalse: {
+              type: 'boolean',
+            },
+          },
+        }],
+        additionalItems: false,
+      }, {
+        type: 'array',
+        items: [{
+          enum: [NEVER],
+        }, {
+          type: 'object',
+          additionalProperties: false,
+          properties: {
+            [ALWAYS]: exceptionsSchema,
+            assumeUndefinedIsFalse: {
+              type: 'boolean',
+            },
+          },
+        }],
+        additionalItems: false,
+      }],
+    },
+  },
+
+  create(context) {
+    const configuration = context.options[0] || NEVER;
+    const configObject = context.options[1] || {};
+    const exceptions = new Set((configuration === ALWAYS ? configObject[NEVER] : configObject[ALWAYS]) || []);
+
+    return {
+      JSXAttribute(node) {
+        const propName = node.name && node.name.name;
+        const value = node.value;
+
+        if (
+          isAlways(configuration, exceptions, propName)
+          && value === null
+        ) {
+          const messageId = 'setBoolean';
+          const data = { propName };
+          report(context, messages[messageId], messageId, {
+            node,
+            data,
+            fix(fixer) {
+              return fixer.insertTextAfter(node, '={true}');
+            },
+          });
+        }
+        if (
+          isNever(configuration, exceptions, propName)
+          && value
+          && value.type === 'JSXExpressionContainer'
+          && value.expression.value === true
+        ) {
+          const messageId = 'omitBoolean';
+          const data = { propName };
+          report(context, messages[messageId], messageId, {
+            node,
+            data,
+            fix(fixer) {
+              return fixer.removeRange([node.name.range[1], value.range[1]]);
+            },
+          });
+        }
+        if (
+          isNever(configuration, exceptions, propName)
+          && configObject.assumeUndefinedIsFalse
+          && value
+          && value.type === 'JSXExpressionContainer'
+          && value.expression.value === false
+        ) {
+          const messageId = 'omitPropAndBoolean';
+          const data = { propName };
+          report(context, messages[messageId], messageId, {
+            node,
+            data,
+            fix(fixer) {
+              return fixer.removeRange([node.name.range[0], value.range[1]]);
+            },
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-child-element-spacing.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-child-element-spacing.d.ts","sourceRoot":"","sources":["jsx-child-element-spacing.js"],"names":[],"mappings":"wBA8CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-child-element-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
+
+// Note: 'br' is not included because whitespace around br tags is inconsequential to the rendered output
+const INLINE_ELEMENTS = new Set([
+  'a',
+  'abbr',
+  'acronym',
+  'b',
+  'bdo',
+  'big',
+  'button',
+  'cite',
+  'code',
+  'dfn',
+  'em',
+  'i',
+  'img',
+  'input',
+  'kbd',
+  'label',
+  'map',
+  'object',
+  'q',
+  'samp',
+  'script',
+  'select',
+  'small',
+  'span',
+  'strong',
+  'sub',
+  'sup',
+  'textarea',
+  'tt',
+  'var',
+]);
+
+const messages = {
+  spacingAfterPrev: 'Ambiguous spacing after previous element {{element}}',
+  spacingBeforeNext: 'Ambiguous spacing before next element {{element}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-child-element-spacing'),
+    },
+    fixable: null,
+
+    messages,
+
+    schema: [],
+  },
+  create(context) {
+    const TEXT_FOLLOWING_ELEMENT_PATTERN = /^\s*\n\s*\S/;
+    const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
+
+    const elementName = (node) => (
+      node.openingElement
+      && node.openingElement.name
+      && node.openingElement.name.type === 'JSXIdentifier'
+      && node.openingElement.name.name
+    );
+
+    const isInlineElement = (node) => (
+      node.type === 'JSXElement'
+      && INLINE_ELEMENTS.has(elementName(node))
+    );
+
+    const handleJSX = (node) => {
+      let lastChild = null;
+      let child = null;
+      (node.children.concat([null])).forEach((nextChild) => {
+        if (
+          (lastChild || nextChild)
+          && (!lastChild || isInlineElement(lastChild))
+          && (child && (child.type === 'Literal' || child.type === 'JSXText'))
+          && (!nextChild || isInlineElement(nextChild))
+          && true
+        ) {
+          if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
+            report(context, messages.spacingAfterPrev, 'spacingAfterPrev', {
+              node: lastChild,
+              loc: lastChild.loc.end,
+              data: {
+                element: elementName(lastChild),
+              },
+            });
+          } else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
+            report(context, messages.spacingBeforeNext, 'spacingBeforeNext', {
+              node: nextChild,
+              loc: nextChild.loc.start,
+              data: {
+                element: elementName(nextChild),
+              },
+            });
+          }
+        }
+        lastChild = child;
+        child = nextChild;
+      });
+    };
+
+    return {
+      JSXElement: handleJSX,
+      JSXFragment: handleJSX,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-closing-bracket-location.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-closing-bracket-location.d.ts","sourceRoot":"","sources":["jsx-closing-bracket-location.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,315 @@
+/**
+ * @fileoverview Validate closing bracket location in JSX
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const has = require('hasown');
+const repeat = require('string.prototype.repeat');
+
+const docsUrl = require('../util/docsUrl');
+const getSourceCode = require('../util/eslint').getSourceCode;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  bracketLocation: 'The closing bracket must be {{location}}{{details}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce closing bracket location in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-closing-bracket-location'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      anyOf: [
+        {
+          enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'],
+        },
+        {
+          type: 'object',
+          properties: {
+            location: {
+              enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'],
+            },
+          },
+          additionalProperties: false,
+        }, {
+          type: 'object',
+          properties: {
+            nonEmpty: {
+              enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false],
+            },
+            selfClosing: {
+              enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false],
+            },
+          },
+          additionalProperties: false,
+        },
+      ],
+    }],
+  },
+
+  create(context) {
+    const MESSAGE_LOCATION = {
+      'after-props': 'placed after the last prop',
+      'after-tag': 'placed after the opening tag',
+      'props-aligned': 'aligned with the last prop',
+      'tag-aligned': 'aligned with the opening tag',
+      'line-aligned': 'aligned with the line containing the opening tag',
+    };
+    const DEFAULT_LOCATION = 'tag-aligned';
+
+    const config = context.options[0];
+    const options = {
+      nonEmpty: DEFAULT_LOCATION,
+      selfClosing: DEFAULT_LOCATION,
+    };
+
+    if (typeof config === 'string') {
+      // simple shorthand [1, 'something']
+      options.nonEmpty = config;
+      options.selfClosing = config;
+    } else if (typeof config === 'object') {
+      // [1, {location: 'something'}] (back-compat)
+      if (has(config, 'location')) {
+        options.nonEmpty = config.location;
+        options.selfClosing = config.location;
+      }
+      // [1, {nonEmpty: 'something'}]
+      if (has(config, 'nonEmpty')) {
+        options.nonEmpty = config.nonEmpty;
+      }
+      // [1, {selfClosing: 'something'}]
+      if (has(config, 'selfClosing')) {
+        options.selfClosing = config.selfClosing;
+      }
+    }
+
+    /**
+     * Get expected location for the closing bracket
+     * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
+     * @return {string} Expected location for the closing bracket
+     */
+    function getExpectedLocation(tokens) {
+      let location;
+      // Is always after the opening tag if there is no props
+      if (typeof tokens.lastProp === 'undefined') {
+        location = 'after-tag';
+      // Is always after the last prop if this one is on the same line as the opening bracket
+      } else if (tokens.opening.line === tokens.lastProp.lastLine) {
+        location = 'after-props';
+      // Else use configuration dependent on selfClosing property
+      } else {
+        location = tokens.selfClosing ? options.selfClosing : options.nonEmpty;
+      }
+      return location;
+    }
+
+    /**
+     * Get the correct 0-indexed column for the closing bracket, given the
+     * expected location.
+     * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
+     * @param {string} expectedLocation Expected location for the closing bracket
+     * @return {?Number} The correct column for the closing bracket, or null
+     */
+    function getCorrectColumn(tokens, expectedLocation) {
+      switch (expectedLocation) {
+        case 'props-aligned':
+          return tokens.lastProp.column;
+        case 'tag-aligned':
+          return tokens.opening.column;
+        case 'line-aligned':
+          return tokens.openingStartOfLine.column;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Check if the closing bracket is correctly located
+     * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
+     * @param {string} expectedLocation Expected location for the closing bracket
+     * @return {boolean} True if the closing bracket is correctly located, false if not
+     */
+    function hasCorrectLocation(tokens, expectedLocation) {
+      switch (expectedLocation) {
+        case 'after-tag':
+          return tokens.tag.line === tokens.closing.line;
+        case 'after-props':
+          return tokens.lastProp.lastLine === tokens.closing.line;
+        case 'props-aligned':
+        case 'tag-aligned':
+        case 'line-aligned': {
+          const correctColumn = getCorrectColumn(tokens, expectedLocation);
+          return correctColumn === tokens.closing.column;
+        }
+        default:
+          return true;
+      }
+    }
+
+    /**
+     * Get the characters used for indentation on the line to be matched
+     * @param {Object} tokens Locations of the opening bracket, closing bracket and last prop
+     * @param {string} expectedLocation Expected location for the closing bracket
+     * @param {number} [correctColumn] Expected column for the closing bracket. Default to 0
+     * @return {string} The characters used for indentation
+     */
+    function getIndentation(tokens, expectedLocation, correctColumn) {
+      const newColumn = correctColumn || 0;
+      let indentation;
+      let spaces = '';
+      switch (expectedLocation) {
+        case 'props-aligned':
+          indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.lastProp.firstLine - 1])[0];
+          break;
+        case 'tag-aligned':
+        case 'line-aligned':
+          indentation = /^\s*/.exec(getSourceCode(context).lines[tokens.opening.line - 1])[0];
+          break;
+        default:
+          indentation = '';
+      }
+      if (indentation.length + 1 < newColumn) {
+        // Non-whitespace characters were included in the column offset
+        spaces = repeat(' ', +correctColumn - indentation.length);
+      }
+      return indentation + spaces;
+    }
+
+    /**
+     * Get the locations of the opening bracket, closing bracket, last prop, and
+     * start of opening line.
+     * @param {ASTNode} node The node to check
+     * @return {Object} Locations of the opening bracket, closing bracket, last
+     * prop and start of opening line.
+     */
+    function getTokensLocations(node) {
+      const sourceCode = getSourceCode(context);
+      const opening = sourceCode.getFirstToken(node).loc.start;
+      const closing = sourceCode.getLastTokens(node, node.selfClosing ? 2 : 1)[0].loc.start;
+      const tag = sourceCode.getFirstToken(node.name).loc.start;
+      let lastProp;
+      if (node.attributes.length) {
+        lastProp = node.attributes[node.attributes.length - 1];
+        lastProp = {
+          column: sourceCode.getFirstToken(lastProp).loc.start.column,
+          firstLine: sourceCode.getFirstToken(lastProp).loc.start.line,
+          lastLine: sourceCode.getLastToken(lastProp).loc.end.line,
+        };
+      }
+      const openingLine = sourceCode.lines[opening.line - 1];
+      const closingLine = sourceCode.lines[closing.line - 1];
+      const isTab = {
+        openTab: /^\t/.test(openingLine),
+        closeTab: /^\t/.test(closingLine),
+      };
+      const openingStartOfLine = {
+        column: /^\s*/.exec(openingLine)[0].length,
+        line: opening.line,
+      };
+      return {
+        isTab,
+        tag,
+        opening,
+        closing,
+        lastProp,
+        selfClosing: node.selfClosing,
+        openingStartOfLine,
+      };
+    }
+
+    /**
+     * Get an unique ID for a given JSXOpeningElement
+     *
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {string} Unique ID (based on its range)
+     */
+    function getOpeningElementId(node) {
+      return node.range.join(':');
+    }
+
+    const lastAttributeNode = {};
+
+    return {
+      JSXAttribute(node) {
+        lastAttributeNode[getOpeningElementId(node.parent)] = node;
+      },
+
+      JSXSpreadAttribute(node) {
+        lastAttributeNode[getOpeningElementId(node.parent)] = node;
+      },
+
+      'JSXOpeningElement:exit'(node) {
+        const attributeNode = lastAttributeNode[getOpeningElementId(node)];
+        const cachedLastAttributeEndPos = attributeNode ? attributeNode.range[1] : null;
+
+        let expectedNextLine;
+        const tokens = getTokensLocations(node);
+        const expectedLocation = getExpectedLocation(tokens);
+        let usingSameIndentation = true;
+
+        if (expectedLocation === 'tag-aligned') {
+          usingSameIndentation = tokens.isTab.openTab === tokens.isTab.closeTab;
+        }
+
+        if (hasCorrectLocation(tokens, expectedLocation) && usingSameIndentation) {
+          return;
+        }
+
+        const data = {
+          location: MESSAGE_LOCATION[expectedLocation],
+          details: '',
+        };
+        const correctColumn = getCorrectColumn(tokens, expectedLocation);
+
+        if (correctColumn !== null) {
+          expectedNextLine = tokens.lastProp
+            && (tokens.lastProp.lastLine === tokens.closing.line);
+          data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? ' on the next line)' : ')'}`;
+        }
+
+        report(context, messages.bracketLocation, 'bracketLocation', {
+          node,
+          loc: tokens.closing,
+          data,
+          fix(fixer) {
+            const closingTag = tokens.selfClosing ? '/>' : '>';
+            switch (expectedLocation) {
+              case 'after-tag':
+                if (cachedLastAttributeEndPos) {
+                  return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]],
+                    (expectedNextLine ? '\n' : '') + closingTag);
+                }
+                return fixer.replaceTextRange([node.name.range[1], node.range[1]],
+                  (expectedNextLine ? '\n' : ' ') + closingTag);
+              case 'after-props':
+                return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]],
+                  (expectedNextLine ? '\n' : '') + closingTag);
+              case 'props-aligned':
+              case 'tag-aligned':
+              case 'line-aligned':
+                return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]],
+                  `\n${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`);
+              default:
+                return true;
+            }
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-closing-tag-location.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-closing-tag-location.d.ts","sourceRoot":"","sources":["jsx-closing-tag-location.js"],"names":[],"mappings":"wBAgCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-closing-tag-location.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,142 @@
+/**
+ * @fileoverview Validate closing tag location in JSX
+ * @author Ross Solomon
+ */
+
+'use strict';
+
+const repeat = require('string.prototype.repeat');
+const has = require('hasown');
+
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const getSourceCode = require('../util/eslint').getSourceCode;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  onOwnLine: 'Closing tag of a multiline JSX expression must be on its own line.',
+  matchIndent: 'Expected closing tag to match indentation of opening.',
+  alignWithOpening: 'Expected closing tag to be aligned with the line containing the opening tag',
+};
+
+const defaultOption = 'tag-aligned';
+
+const optionMessageMap = {
+  'tag-aligned': 'matchIndent',
+  'line-aligned': 'alignWithOpening',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce closing tag location for multiline JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-closing-tag-location'),
+    },
+    fixable: 'whitespace',
+    messages,
+    schema: [{
+      anyOf: [
+        {
+          enum: ['tag-aligned', 'line-aligned'],
+        },
+        {
+          type: 'object',
+          properties: {
+            location: {
+              enum: ['tag-aligned', 'line-aligned'],
+            },
+          },
+          additionalProperties: false,
+        },
+      ],
+    }],
+  },
+
+  create(context) {
+    const config = context.options[0];
+    let option = defaultOption;
+
+    if (typeof config === 'string') {
+      option = config;
+    } else if (typeof config === 'object') {
+      if (has(config, 'location')) {
+        option = config.location;
+      }
+    }
+
+    function getIndentation(openingStartOfLine, opening) {
+      if (option === 'line-aligned') return openingStartOfLine.column;
+      if (option === 'tag-aligned') return opening.loc.start.column;
+    }
+
+    function handleClosingElement(node) {
+      if (!node.parent) {
+        return;
+      }
+      const sourceCode = getSourceCode(context);
+
+      const opening = node.parent.openingElement || node.parent.openingFragment;
+      const openingLoc = sourceCode.getFirstToken(opening).loc.start;
+      const openingLine = sourceCode.lines[openingLoc.line - 1];
+
+      const openingStartOfLine = {
+        column: /^\s*/.exec(openingLine)[0].length,
+        line: openingLoc.line,
+      };
+
+      if (opening.loc.start.line === node.loc.start.line) {
+        return;
+      }
+
+      if (
+        opening.loc.start.column === node.loc.start.column
+        && option === 'tag-aligned'
+      ) {
+        return;
+      }
+
+      if (
+        openingStartOfLine.column === node.loc.start.column
+        && option === 'line-aligned'
+      ) {
+        return;
+      }
+
+      const messageId = astUtil.isNodeFirstInLine(context, node)
+        ? optionMessageMap[option]
+        : 'onOwnLine';
+
+      report(context, messages[messageId], messageId, {
+        node,
+        loc: node.loc,
+        fix(fixer) {
+          const indent = repeat(
+            ' ',
+            getIndentation(openingStartOfLine, opening)
+          );
+
+          if (astUtil.isNodeFirstInLine(context, node)) {
+            return fixer.replaceTextRange(
+              [node.range[0] - node.loc.start.column, node.range[0]],
+              indent
+            );
+          }
+
+          return fixer.insertTextBefore(node, `\n${indent}`);
+        },
+      });
+    }
+
+    return {
+      JSXClosingElement: handleClosingElement,
+      JSXClosingFragment: handleClosingElement,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-curly-brace-presence.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-curly-brace-presence.d.ts","sourceRoot":"","sources":["jsx-curly-brace-presence.js"],"names":[],"mappings":"wBAgJW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,421 @@
+/**
+ * @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
+ * @author Jacky Ho
+ * @author Simon Lydell
+ */
+
+'use strict';
+
+const arrayIncludes = require('array-includes');
+
+const docsUrl = require('../util/docsUrl');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const OPTION_ALWAYS = 'always';
+const OPTION_NEVER = 'never';
+const OPTION_IGNORE = 'ignore';
+
+const OPTION_VALUES = [
+  OPTION_ALWAYS,
+  OPTION_NEVER,
+  OPTION_IGNORE,
+];
+const DEFAULT_CONFIG = { props: OPTION_NEVER, children: OPTION_NEVER, propElementValues: OPTION_IGNORE };
+
+const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g;
+
+function containsLineTerminators(rawStringValue) {
+  return /[\n\r\u2028\u2029]/.test(rawStringValue);
+}
+
+function containsBackslash(rawStringValue) {
+  return arrayIncludes(rawStringValue, '\\');
+}
+
+function containsHTMLEntity(rawStringValue) {
+  return HTML_ENTITY_REGEX().test(rawStringValue);
+}
+
+function containsOnlyHtmlEntities(rawStringValue) {
+  return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === '';
+}
+
+function containsDisallowedJSXTextChars(rawStringValue) {
+  return /[{<>}]/.test(rawStringValue);
+}
+
+function containsQuoteCharacters(value) {
+  return /['"]/.test(value);
+}
+
+function containsMultilineComment(value) {
+  return /\/\*/.test(value);
+}
+
+function escapeDoubleQuotes(rawStringValue) {
+  return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"');
+}
+
+function escapeBackslashes(rawStringValue) {
+  return rawStringValue.replace(/\\/g, '\\\\');
+}
+
+function needToEscapeCharacterForJSX(raw, node) {
+  return (
+    containsBackslash(raw)
+    || containsHTMLEntity(raw)
+    || (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw))
+  );
+}
+
+function containsWhitespaceExpression(child) {
+  if (child.type === 'JSXExpressionContainer') {
+    const value = child.expression.value;
+    return value ? jsxUtil.isWhiteSpaces(value) : false;
+  }
+  return false;
+}
+
+function isLineBreak(text) {
+  return containsLineTerminators(text) && text.trim() === '';
+}
+
+function wrapNonHTMLEntities(text) {
+  const HTML_ENTITY = '<HTML_ENTITY>';
+  const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => (
+    word === '' ? '' : `{${JSON.stringify(word)}}`
+  )).join(HTML_ENTITY);
+
+  const htmlEntities = text.match(HTML_ENTITY_REGEX());
+  return htmlEntities.reduce((acc, htmlEntity) => (
+    acc.replace(HTML_ENTITY, htmlEntity)
+  ), withCurlyBraces);
+}
+
+function wrapWithCurlyBraces(rawText) {
+  if (!containsLineTerminators(rawText)) {
+    return `{${JSON.stringify(rawText)}}`;
+  }
+
+  return rawText.split('\n').map((line) => {
+    if (line.trim() === '') {
+      return line;
+    }
+    const firstCharIndex = line.search(/[^\s]/);
+    const leftWhitespace = line.slice(0, firstCharIndex);
+    const text = line.slice(firstCharIndex);
+
+    if (containsHTMLEntity(line)) {
+      return `${leftWhitespace}${wrapNonHTMLEntities(text)}`;
+    }
+    return `${leftWhitespace}{${JSON.stringify(text)}}`;
+  }).join('\n');
+}
+
+function isWhiteSpaceLiteral(node) {
+  return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value);
+}
+
+function isStringWithTrailingWhiteSpaces(value) {
+  return /^\s|\s$/.test(value);
+}
+
+function isLiteralWithTrailingWhiteSpaces(node) {
+  return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value);
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  unnecessaryCurly: 'Curly braces are unnecessary here.',
+  missingCurly: 'Need to wrap this literal in a JSX expression.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-curly-brace-presence'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [
+      {
+        anyOf: [
+          {
+            type: 'object',
+            properties: {
+              props: { enum: OPTION_VALUES },
+              children: { enum: OPTION_VALUES },
+              propElementValues: { enum: OPTION_VALUES },
+            },
+            additionalProperties: false,
+          },
+          {
+            enum: OPTION_VALUES,
+          },
+        ],
+      },
+    ],
+  },
+
+  create(context) {
+    const ruleOptions = context.options[0];
+    const userConfig = typeof ruleOptions === 'string'
+      ? { props: ruleOptions, children: ruleOptions, propElementValues: OPTION_IGNORE }
+      : Object.assign({}, DEFAULT_CONFIG, ruleOptions);
+
+    /**
+     * Report and fix an unnecessary curly brace violation on a node
+     * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression
+     */
+    function reportUnnecessaryCurly(JSXExpressionNode) {
+      report(context, messages.unnecessaryCurly, 'unnecessaryCurly', {
+        node: JSXExpressionNode,
+        fix(fixer) {
+          const expression = JSXExpressionNode.expression;
+
+          let textToReplace;
+          if (jsxUtil.isJSX(expression)) {
+            textToReplace = getText(context, expression);
+          } else {
+            const expressionType = expression && expression.type;
+            const parentType = JSXExpressionNode.parent.type;
+
+            if (parentType === 'JSXAttribute') {
+              if (expressionType !== 'TemplateLiteral' && /["]/.test(expression.raw.slice(1, -1))) {
+                textToReplace = expression.raw;
+              } else {
+                textToReplace = `"${expressionType === 'TemplateLiteral'
+                  ? expression.quasis[0].value.raw
+                  : expression.raw.slice(1, -1)
+                }"`;
+              }
+            } else if (jsxUtil.isJSX(expression)) {
+              textToReplace = getText(context, expression);
+            } else {
+              textToReplace = expressionType === 'TemplateLiteral'
+                ? expression.quasis[0].value.cooked : expression.value;
+            }
+          }
+
+          return fixer.replaceText(JSXExpressionNode, textToReplace);
+        },
+      });
+    }
+
+    function reportMissingCurly(literalNode) {
+      report(context, messages.missingCurly, 'missingCurly', {
+        node: literalNode,
+        fix(fixer) {
+          if (jsxUtil.isJSX(literalNode)) {
+            return fixer.replaceText(literalNode, `{${getText(context, literalNode)}}`);
+          }
+
+          // If a HTML entity name is found, bail out because it can be fixed
+          // by either using the real character or the unicode equivalent.
+          // If it contains any line terminator character, bail out as well.
+          if (
+            containsOnlyHtmlEntities(literalNode.raw)
+            || (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw))
+            || isLineBreak(literalNode.raw)
+          ) {
+            return null;
+          }
+
+          const expression = literalNode.parent.type === 'JSXAttribute'
+            ? `{"${escapeDoubleQuotes(escapeBackslashes(
+              literalNode.raw.slice(1, -1)
+            ))}"}`
+            : wrapWithCurlyBraces(literalNode.raw);
+
+          return fixer.replaceText(literalNode, expression);
+        },
+      });
+    }
+
+    // Bail out if there is any character that needs to be escaped in JSX
+    // because escaping decreases readability and the original code may be more
+    // readable anyway or intentional for other specific reasons
+    function lintUnnecessaryCurly(JSXExpressionNode) {
+      const expression = JSXExpressionNode.expression;
+      const expressionType = expression.type;
+
+      const sourceCode = getSourceCode(context);
+      // Curly braces containing comments are necessary
+      if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0) {
+        return;
+      }
+
+      if (
+        (expressionType === 'Literal' || expressionType === 'JSXText')
+          && typeof expression.value === 'string'
+          && (
+            (JSXExpressionNode.parent.type === 'JSXAttribute' && !isWhiteSpaceLiteral(expression))
+            || !isLiteralWithTrailingWhiteSpaces(expression)
+          )
+          && !containsMultilineComment(expression.value)
+          && !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && (
+          jsxUtil.isJSX(JSXExpressionNode.parent)
+          || (!containsQuoteCharacters(expression.value) || typeof expression.value === 'string')
+        )
+      ) {
+        reportUnnecessaryCurly(JSXExpressionNode);
+      } else if (
+        expressionType === 'TemplateLiteral'
+        && expression.expressions.length === 0
+        && expression.quasis[0].value.raw.indexOf('\n') === -1
+        && !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw)
+        && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode)
+        && !containsQuoteCharacters(expression.quasis[0].value.cooked)
+      ) {
+        reportUnnecessaryCurly(JSXExpressionNode);
+      } else if (jsxUtil.isJSX(expression)) {
+        reportUnnecessaryCurly(JSXExpressionNode);
+      }
+    }
+
+    function areRuleConditionsSatisfied(parent, config, ruleCondition) {
+      return (
+        parent.type === 'JSXAttribute'
+          && typeof config.props === 'string'
+          && config.props === ruleCondition
+      ) || (
+        jsxUtil.isJSX(parent)
+          && typeof config.children === 'string'
+          && config.children === ruleCondition
+      );
+    }
+
+    function getAdjacentSiblings(node, children) {
+      for (let i = 1; i < children.length - 1; i++) {
+        const child = children[i];
+        if (node === child) {
+          return [children[i - 1], children[i + 1]];
+        }
+      }
+      if (node === children[0] && children[1]) {
+        return [children[1]];
+      }
+      if (node === children[children.length - 1] && children[children.length - 2]) {
+        return [children[children.length - 2]];
+      }
+      return [];
+    }
+
+    function hasAdjacentJsxExpressionContainers(node, children) {
+      if (!children) {
+        return false;
+      }
+      const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
+      const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
+
+      return adjSiblings.some((x) => x.type && x.type === 'JSXExpressionContainer');
+    }
+    function hasAdjacentJsx(node, children) {
+      if (!children) {
+        return false;
+      }
+      const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
+      const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
+
+      return adjSiblings.some((x) => x.type && arrayIncludes(['JSXExpressionContainer', 'JSXElement'], x.type));
+    }
+    function shouldCheckForUnnecessaryCurly(node, config) {
+      const parent = node.parent;
+      // Bail out if the parent is a JSXAttribute & its contents aren't
+      // StringLiteral or TemplateLiteral since e.g
+      // <App prop1={<CustomEl />} prop2={<CustomEl>...</CustomEl>} />
+
+      if (
+        parent.type && parent.type === 'JSXAttribute'
+        && (node.expression && node.expression.type
+          && node.expression.type !== 'Literal'
+          && node.expression.type !== 'StringLiteral'
+          && node.expression.type !== 'TemplateLiteral')
+      ) {
+        return false;
+      }
+
+      // If there are adjacent `JsxExpressionContainer` then there is no need,
+      // to check for unnecessary curly braces.
+      if (jsxUtil.isJSX(parent) && hasAdjacentJsxExpressionContainers(node, parent.children)) {
+        return false;
+      }
+      if (containsWhitespaceExpression(node) && hasAdjacentJsx(node, parent.children)) {
+        return false;
+      }
+      if (
+        parent.children
+        && parent.children.length === 1
+        && containsWhitespaceExpression(node)
+      ) {
+        return false;
+      }
+
+      return areRuleConditionsSatisfied(parent, config, OPTION_NEVER);
+    }
+
+    function shouldCheckForMissingCurly(node, config) {
+      if (jsxUtil.isJSX(node)) {
+        return config.propElementValues !== OPTION_IGNORE;
+      }
+      if (
+        isLineBreak(node.raw)
+        || containsOnlyHtmlEntities(node.raw)
+      ) {
+        return false;
+      }
+      const parent = node.parent;
+      if (
+        parent.children
+        && parent.children.length === 1
+        && containsWhitespaceExpression(parent.children[0])
+      ) {
+        return false;
+      }
+
+      return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS);
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      'JSXAttribute > JSXExpressionContainer > JSXElement'(node) {
+        if (userConfig.propElementValues === OPTION_NEVER) {
+          reportUnnecessaryCurly(node.parent);
+        }
+      },
+
+      JSXExpressionContainer(node) {
+        if (shouldCheckForUnnecessaryCurly(node, userConfig)) {
+          lintUnnecessaryCurly(node);
+        }
+      },
+
+      'JSXAttribute > JSXElement, Literal, JSXText'(node) {
+        if (shouldCheckForMissingCurly(node, userConfig)) {
+          reportMissingCurly(node);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-curly-newline.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-curly-newline.d.ts","sourceRoot":"","sources":["jsx-curly-newline.js"],"names":[],"mappings":"wBA+CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,187 @@
+/**
+ * @fileoverview enforce consistent line breaks inside jsx curly
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const report = require('../util/report');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function getNormalizedOption(context) {
+  const rawOption = context.options[0] || 'consistent';
+
+  if (rawOption === 'consistent') {
+    return {
+      multiline: 'consistent',
+      singleline: 'consistent',
+    };
+  }
+
+  if (rawOption === 'never') {
+    return {
+      multiline: 'forbid',
+      singleline: 'forbid',
+    };
+  }
+
+  return {
+    multiline: rawOption.multiline || 'consistent',
+    singleline: rawOption.singleline || 'consistent',
+  };
+}
+
+const messages = {
+  expectedBefore: 'Expected newline before \'}\'.',
+  expectedAfter: 'Expected newline after \'{\'.',
+  unexpectedBefore: 'Unexpected newline before \'}\'.',
+  unexpectedAfter: 'Unexpected newline after \'{\'.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    type: 'layout',
+
+    docs: {
+      description: 'Enforce consistent linebreaks in curly braces in JSX attributes and expressions',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-curly-newline'),
+    },
+
+    fixable: 'whitespace',
+
+    schema: [
+      {
+        anyOf: [
+          {
+            enum: ['consistent', 'never'],
+          },
+          {
+            type: 'object',
+            properties: {
+              singleline: { enum: ['consistent', 'require', 'forbid'] },
+              multiline: { enum: ['consistent', 'require', 'forbid'] },
+            },
+            additionalProperties: false,
+          },
+        ],
+      },
+    ],
+
+    messages,
+  },
+
+  create(context) {
+    const sourceCode = getSourceCode(context);
+    const option = getNormalizedOption(context);
+
+    // ----------------------------------------------------------------------
+    // Helpers
+    // ----------------------------------------------------------------------
+
+    /**
+     * Determines whether two adjacent tokens are on the same line.
+     * @param {Object} left - The left token object.
+     * @param {Object} right - The right token object.
+     * @returns {boolean} Whether or not the tokens are on the same line.
+     */
+    function isTokenOnSameLine(left, right) {
+      return left.loc.end.line === right.loc.start.line;
+    }
+
+    /**
+     * Determines whether there should be newlines inside curlys
+     * @param {ASTNode} expression The expression contained in the curlys
+     * @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code.
+     * @returns {boolean} `true` if there should be newlines inside the function curlys
+     */
+    function shouldHaveNewlines(expression, hasLeftNewline) {
+      const isMultiline = expression.loc.start.line !== expression.loc.end.line;
+
+      switch (isMultiline ? option.multiline : option.singleline) {
+        case 'forbid': return false;
+        case 'require': return true;
+        case 'consistent':
+        default: return hasLeftNewline;
+      }
+    }
+
+    /**
+     * Validates curlys
+     * @param {Object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
+     * @param {ASTNode} expression The expression inside the curly
+     * @returns {void}
+     */
+    function validateCurlys(curlys, expression) {
+      const leftCurly = curlys.leftCurly;
+      const rightCurly = curlys.rightCurly;
+      const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly);
+      const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly);
+      const hasLeftNewline = !isTokenOnSameLine(leftCurly, tokenAfterLeftCurly);
+      const hasRightNewline = !isTokenOnSameLine(tokenBeforeRightCurly, rightCurly);
+      const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
+
+      if (hasLeftNewline && !needsNewlines) {
+        report(context, messages.unexpectedAfter, 'unexpectedAfter', {
+          node: leftCurly,
+          fix(fixer) {
+            return getText(context)
+              .slice(leftCurly.range[1], tokenAfterLeftCurly.range[0])
+              .trim()
+              ? null // If there is a comment between the { and the first element, don't do a fix.
+              : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
+          },
+        });
+      } else if (!hasLeftNewline && needsNewlines) {
+        report(context, messages.expectedAfter, 'expectedAfter', {
+          node: leftCurly,
+          fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n'),
+        });
+      }
+
+      if (hasRightNewline && !needsNewlines) {
+        report(context, messages.unexpectedBefore, 'unexpectedBefore', {
+          node: rightCurly,
+          fix(fixer) {
+            return getText(context)
+              .slice(tokenBeforeRightCurly.range[1], rightCurly.range[0])
+              .trim()
+              ? null // If there is a comment between the last element and the }, don't do a fix.
+              : fixer.removeRange([
+                tokenBeforeRightCurly.range[1],
+                rightCurly.range[0],
+              ]);
+          },
+        });
+      } else if (!hasRightNewline && needsNewlines) {
+        report(context, messages.expectedBefore, 'expectedBefore', {
+          node: rightCurly,
+          fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n'),
+        });
+      }
+    }
+
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+
+    return {
+      JSXExpressionContainer(node) {
+        const curlyTokens = {
+          leftCurly: sourceCode.getFirstToken(node),
+          rightCurly: sourceCode.getLastToken(node),
+        };
+        validateCurlys(curlyTokens, node.expression);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-curly-spacing.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-curly-spacing.d.ts","sourceRoot":"","sources":["jsx-curly-spacing.js"],"names":[],"mappings":"wBAqCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,432 @@
+/**
+ * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
+ * @author Jamund Ferguson
+ * @author Brandyn Bennett
+ * @author Michael Ficarra
+ * @author Vignesh Anand
+ * @author Jamund Ferguson
+ * @author Yannick Croissant
+ * @author Erik Wendel
+ */
+
+'use strict';
+
+const has = require('hasown');
+const docsUrl = require('../util/docsUrl');
+const getSourceCode = require('../util/eslint').getSourceCode;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const SPACING = {
+  always: 'always',
+  never: 'never',
+};
+const SPACING_VALUES = [SPACING.always, SPACING.never];
+
+const messages = {
+  noNewlineAfter: 'There should be no newline after \'{{token}}\'',
+  noNewlineBefore: 'There should be no newline before \'{{token}}\'',
+  noSpaceAfter: 'There should be no space after \'{{token}}\'',
+  noSpaceBefore: 'There should be no space before \'{{token}}\'',
+  spaceNeededAfter: 'A space is required after \'{{token}}\'',
+  spaceNeededBefore: 'A space is required before \'{{token}}\'',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-curly-spacing'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: {
+      definitions: {
+        basicConfig: {
+          type: 'object',
+          properties: {
+            when: {
+              enum: SPACING_VALUES,
+            },
+            allowMultiline: {
+              type: 'boolean',
+            },
+            spacing: {
+              type: 'object',
+              properties: {
+                objectLiterals: {
+                  enum: SPACING_VALUES,
+                },
+              },
+            },
+          },
+        },
+        basicConfigOrBoolean: {
+          anyOf: [{
+            $ref: '#/definitions/basicConfig',
+          }, {
+            type: 'boolean',
+          }],
+        },
+      },
+      type: 'array',
+      items: [{
+        anyOf: [{
+          allOf: [{
+            $ref: '#/definitions/basicConfig',
+          }, {
+            type: 'object',
+            properties: {
+              attributes: {
+                $ref: '#/definitions/basicConfigOrBoolean',
+              },
+              children: {
+                $ref: '#/definitions/basicConfigOrBoolean',
+              },
+            },
+          }],
+        }, {
+          enum: SPACING_VALUES,
+        }],
+      }, {
+        type: 'object',
+        properties: {
+          allowMultiline: {
+            type: 'boolean',
+          },
+          spacing: {
+            type: 'object',
+            properties: {
+              objectLiterals: {
+                enum: SPACING_VALUES,
+              },
+            },
+          },
+        },
+        additionalProperties: false,
+      }],
+    },
+  },
+
+  create(context) {
+    function normalizeConfig(configOrTrue, defaults, lastPass) {
+      const config = configOrTrue === true ? {} : configOrTrue;
+      const when = config.when || defaults.when;
+      const allowMultiline = has(config, 'allowMultiline') ? config.allowMultiline : defaults.allowMultiline;
+      const spacing = config.spacing || {};
+      let objectLiteralSpaces = spacing.objectLiterals || defaults.objectLiteralSpaces;
+      if (lastPass) {
+        // On the final pass assign the values that should be derived from others if they are still undefined
+        objectLiteralSpaces = objectLiteralSpaces || when;
+      }
+
+      return {
+        when,
+        allowMultiline,
+        objectLiteralSpaces,
+      };
+    }
+
+    const DEFAULT_WHEN = SPACING.never;
+    const DEFAULT_ALLOW_MULTILINE = true;
+    const DEFAULT_ATTRIBUTES = true;
+    const DEFAULT_CHILDREN = false;
+
+    let originalConfig = context.options[0] || {};
+    if (SPACING_VALUES.indexOf(originalConfig) !== -1) {
+      originalConfig = Object.assign({ when: context.options[0] }, context.options[1]);
+    }
+    const defaultConfig = normalizeConfig(originalConfig, {
+      when: DEFAULT_WHEN,
+      allowMultiline: DEFAULT_ALLOW_MULTILINE,
+    });
+    const attributes = has(originalConfig, 'attributes') ? originalConfig.attributes : DEFAULT_ATTRIBUTES;
+    const attributesConfig = attributes ? normalizeConfig(attributes, defaultConfig, true) : null;
+    const children = has(originalConfig, 'children') ? originalConfig.children : DEFAULT_CHILDREN;
+    const childrenConfig = children ? normalizeConfig(children, defaultConfig, true) : null;
+
+    // --------------------------------------------------------------------------
+    // Helpers
+    // --------------------------------------------------------------------------
+
+    /**
+     * Determines whether two adjacent tokens have a newline between them.
+     * @param {Object} left - The left token object.
+     * @param {Object} right - The right token object.
+     * @returns {boolean} Whether or not there is a newline between the tokens.
+     */
+    function isMultiline(left, right) {
+      return left.loc.end.line !== right.loc.start.line;
+    }
+
+    /**
+     * Trims text of whitespace between two ranges
+     * @param {Fixer} fixer - the eslint fixer object
+     * @param {number} fromLoc - the start location
+     * @param {number} toLoc - the end location
+     * @param {string} mode - either 'start' or 'end'
+     * @param {string=} spacing - a spacing value that will optionally add a space to the removed text
+     * @returns {Object|*|{range, text}}
+     */
+    function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing) {
+      let replacementText = getSourceCode(context).text.slice(fromLoc, toLoc);
+      if (mode === 'start') {
+        replacementText = replacementText.replace(/^\s+/gm, '');
+      } else {
+        replacementText = replacementText.replace(/\s+$/gm, '');
+      }
+      if (spacing === SPACING.always) {
+        if (mode === 'start') {
+          replacementText += ' ';
+        } else {
+          replacementText = ` ${replacementText}`;
+        }
+      }
+      return fixer.replaceTextRange([fromLoc, toLoc], replacementText);
+    }
+
+    /**
+    * Reports that there shouldn't be a newline after the first token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @param {string} spacing
+    * @returns {void}
+    */
+    function reportNoBeginningNewline(node, token, spacing) {
+      report(context, messages.noNewlineAfter, 'noNewlineAfter', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          const nextToken = getSourceCode(context).getTokenAfter(token);
+          return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start', spacing);
+        },
+      });
+    }
+
+    /**
+    * Reports that there shouldn't be a newline before the last token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @param {string} spacing
+    * @returns {void}
+    */
+    function reportNoEndingNewline(node, token, spacing) {
+      report(context, messages.noNewlineBefore, 'noNewlineBefore', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          const previousToken = getSourceCode(context).getTokenBefore(token);
+          return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end', spacing);
+        },
+      });
+    }
+
+    /**
+    * Reports that there shouldn't be a space after the first token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @returns {void}
+    */
+    function reportNoBeginningSpace(node, token) {
+      report(context, messages.noSpaceAfter, 'noSpaceAfter', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          const sourceCode = getSourceCode(context);
+          const nextToken = sourceCode.getTokenAfter(token);
+          let nextComment;
+
+          // eslint >=4.x
+          if (sourceCode.getCommentsAfter) {
+            nextComment = sourceCode.getCommentsAfter(token);
+          // eslint 3.x
+          } else {
+            const potentialComment = sourceCode.getTokenAfter(token, { includeComments: true });
+            nextComment = nextToken === potentialComment ? [] : [potentialComment];
+          }
+
+          // Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
+          if (nextComment.length > 0) {
+            return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), 'start');
+          }
+
+          return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start');
+        },
+      });
+    }
+
+    /**
+    * Reports that there shouldn't be a space before the last token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @returns {void}
+    */
+    function reportNoEndingSpace(node, token) {
+      report(context, messages.noSpaceBefore, 'noSpaceBefore', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          const sourceCode = getSourceCode(context);
+          const previousToken = sourceCode.getTokenBefore(token);
+          let previousComment;
+
+          // eslint >=4.x
+          if (sourceCode.getCommentsBefore) {
+            previousComment = sourceCode.getCommentsBefore(token);
+          // eslint 3.x
+          } else {
+            const potentialComment = sourceCode.getTokenBefore(token, { includeComments: true });
+            previousComment = previousToken === potentialComment ? [] : [potentialComment];
+          }
+
+          // Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
+          if (previousComment.length > 0) {
+            return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], 'end');
+          }
+
+          return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end');
+        },
+      });
+    }
+
+    /**
+    * Reports that there should be a space after the first token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @returns {void}
+    */
+    function reportRequiredBeginningSpace(node, token) {
+      report(context, messages.spaceNeededAfter, 'spaceNeededAfter', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          return fixer.insertTextAfter(token, ' ');
+        },
+      });
+    }
+
+    /**
+    * Reports that there should be a space before the last token
+    * @param {ASTNode} node - The node to report in the event of an error.
+    * @param {Token} token - The token to use for the report.
+    * @returns {void}
+    */
+    function reportRequiredEndingSpace(node, token) {
+      report(context, messages.spaceNeededBefore, 'spaceNeededBefore', {
+        node,
+        loc: token.loc.start,
+        data: {
+          token: token.value,
+        },
+        fix(fixer) {
+          return fixer.insertTextBefore(token, ' ');
+        },
+      });
+    }
+
+    /**
+     * Determines if spacing in curly braces is valid.
+     * @param {ASTNode} node The AST node to check.
+     * @returns {void}
+     */
+    function validateBraceSpacing(node) {
+      let config;
+      switch (node.parent.type) {
+        case 'JSXAttribute':
+        case 'JSXOpeningElement':
+          config = attributesConfig;
+          break;
+
+        case 'JSXElement':
+        case 'JSXFragment':
+          config = childrenConfig;
+          break;
+
+        default:
+          return;
+      }
+      if (config === null) {
+        return;
+      }
+
+      const sourceCode = getSourceCode(context);
+      const first = sourceCode.getFirstToken(node);
+      const last = sourceCode.getLastToken(node);
+      let second = sourceCode.getTokenAfter(first, { includeComments: true });
+      let penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
+
+      if (!second) {
+        second = sourceCode.getTokenAfter(first);
+        const leadingComments = sourceCode.getNodeByRangeIndex(second.range[0]).leadingComments;
+        second = leadingComments ? leadingComments[0] : second;
+      }
+      if (!penultimate) {
+        penultimate = sourceCode.getTokenBefore(last);
+        const trailingComments = sourceCode.getNodeByRangeIndex(penultimate.range[0]).trailingComments;
+        penultimate = trailingComments ? trailingComments[trailingComments.length - 1] : penultimate;
+      }
+
+      const isObjectLiteral = first.value === second.value;
+      const spacing = isObjectLiteral ? config.objectLiteralSpaces : config.when;
+      if (spacing === SPACING.always) {
+        if (!sourceCode.isSpaceBetweenTokens(first, second)) {
+          reportRequiredBeginningSpace(node, first);
+        } else if (!config.allowMultiline && isMultiline(first, second)) {
+          reportNoBeginningNewline(node, first, spacing);
+        }
+        if (!sourceCode.isSpaceBetweenTokens(penultimate, last)) {
+          reportRequiredEndingSpace(node, last);
+        } else if (!config.allowMultiline && isMultiline(penultimate, last)) {
+          reportNoEndingNewline(node, last, spacing);
+        }
+      } else if (spacing === SPACING.never) {
+        if (isMultiline(first, second)) {
+          if (!config.allowMultiline) {
+            reportNoBeginningNewline(node, first, spacing);
+          }
+        } else if (sourceCode.isSpaceBetweenTokens(first, second)) {
+          reportNoBeginningSpace(node, first);
+        }
+        if (isMultiline(penultimate, last)) {
+          if (!config.allowMultiline) {
+            reportNoEndingNewline(node, last, spacing);
+          }
+        } else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) {
+          reportNoEndingSpace(node, last);
+        }
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXExpressionContainer: validateBraceSpacing,
+      JSXSpreadAttribute: validateBraceSpacing,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-equals-spacing.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-equals-spacing.d.ts","sourceRoot":"","sources":["jsx-equals-spacing.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-equals-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,112 @@
+/**
+ * @fileoverview Disallow or enforce spaces around equal signs in JSX attributes.
+ * @author ryym
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getSourceCode = require('../util/eslint').getSourceCode;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noSpaceBefore: 'There should be no space before \'=\'',
+  noSpaceAfter: 'There should be no space after \'=\'',
+  needSpaceBefore: 'A space is required before \'=\'',
+  needSpaceAfter: 'A space is required after \'=\'',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce or disallow spaces around equal signs in JSX attributes',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-equals-spacing'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      enum: ['always', 'never'],
+    }],
+  },
+
+  create(context) {
+    const config = context.options[0] || 'never';
+
+    /**
+     * Determines a given attribute node has an equal sign.
+     * @param {ASTNode} attrNode - The attribute node.
+     * @returns {boolean} Whether or not the attriute node has an equal sign.
+     */
+    function hasEqual(attrNode) {
+      return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null;
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXOpeningElement(node) {
+        node.attributes.forEach((attrNode) => {
+          if (!hasEqual(attrNode)) {
+            return;
+          }
+
+          const sourceCode = getSourceCode(context);
+          const equalToken = sourceCode.getTokenAfter(attrNode.name);
+          const spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken);
+          const spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value);
+
+          if (config === 'never') {
+            if (spacedBefore) {
+              report(context, messages.noSpaceBefore, 'noSpaceBefore', {
+                node: attrNode,
+                loc: equalToken.loc.start,
+                fix(fixer) {
+                  return fixer.removeRange([attrNode.name.range[1], equalToken.range[0]]);
+                },
+              });
+            }
+            if (spacedAfter) {
+              report(context, messages.noSpaceAfter, 'noSpaceAfter', {
+                node: attrNode,
+                loc: equalToken.loc.start,
+                fix(fixer) {
+                  return fixer.removeRange([equalToken.range[1], attrNode.value.range[0]]);
+                },
+              });
+            }
+          } else if (config === 'always') {
+            if (!spacedBefore) {
+              report(context, messages.needSpaceBefore, 'needSpaceBefore', {
+                node: attrNode,
+                loc: equalToken.loc.start,
+                fix(fixer) {
+                  return fixer.insertTextBefore(equalToken, ' ');
+                },
+              });
+            }
+            if (!spacedAfter) {
+              report(context, messages.needSpaceAfter, 'needSpaceAfter', {
+                node: attrNode,
+                loc: equalToken.loc.start,
+                fix(fixer) {
+                  return fixer.insertTextAfter(equalToken, ' ');
+                },
+              });
+            }
+          }
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-filename-extension.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-filename-extension.d.ts","sourceRoot":"","sources":["jsx-filename-extension.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-filename-extension.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,120 @@
+/**
+ * @fileoverview Restrict file extensions that may contain JSX
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+const path = require('path');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = {
+  allow: 'always',
+  extensions: ['.jsx'],
+  ignoreFilesWithoutCode: false,
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noJSXWithExtension: 'JSX not allowed in files with extension \'{{ext}}\'',
+  extensionOnlyForJSX: 'Only files containing JSX may use the extension \'{{ext}}\'',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow file extensions that may contain JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-filename-extension'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allow: {
+          enum: ['always', 'as-needed'],
+        },
+        extensions: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        ignoreFilesWithoutCode: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const filename = context.getFilename();
+
+    let jsxNode;
+
+    if (filename === '<text>') {
+      // No need to traverse any nodes.
+      return {};
+    }
+
+    const allow = (context.options[0] && context.options[0].allow) || DEFAULTS.allow;
+    const allowedExtensions = (context.options[0] && context.options[0].extensions) || DEFAULTS.extensions;
+    const ignoreFilesWithoutCode = (context.options[0] && context.options[0].ignoreFilesWithoutCode)
+      || DEFAULTS.ignoreFilesWithoutCode;
+    const isAllowedExtension = allowedExtensions.some((extension) => filename.slice(-extension.length) === extension);
+
+    function handleJSX(node) {
+      if (!jsxNode) {
+        jsxNode = node;
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXElement: handleJSX,
+      JSXFragment: handleJSX,
+
+      'Program:exit'(node) {
+        if (jsxNode) {
+          if (!isAllowedExtension) {
+            report(context, messages.noJSXWithExtension, 'noJSXWithExtension', {
+              node: jsxNode,
+              data: {
+                ext: path.extname(filename),
+              },
+            });
+          }
+          return;
+        }
+
+        if (isAllowedExtension && allow === 'as-needed') {
+          if (ignoreFilesWithoutCode && node.body.length === 0) {
+            return;
+          }
+          report(context, messages.extensionOnlyForJSX, 'extensionOnlyForJSX', {
+            node,
+            data: {
+              ext: path.extname(filename),
+            },
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-first-prop-new-line.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-first-prop-new-line.d.ts","sourceRoot":"","sources":["jsx-first-prop-new-line.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Ensure proper position of the first property in JSX
+ * @author Joachim Seminck
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const propsUtil = require('../util/props');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  propOnNewLine: 'Property should be placed on a new line',
+  propOnSameLine: 'Property should be placed on the same line as the component declaration',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce proper position of the first property in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-first-prop-new-line'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      enum: ['always', 'never', 'multiline', 'multiline-multiprop', 'multiprop'],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || 'multiline-multiprop';
+
+    function isMultilineJSX(jsxNode) {
+      return jsxNode.loc.start.line < jsxNode.loc.end.line;
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        if (
+          (configuration === 'multiline' && isMultilineJSX(node))
+          || (configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1)
+          || (configuration === 'multiprop' && node.attributes.length > 1)
+          || (configuration === 'always')
+        ) {
+          node.attributes.some((decl) => {
+            if (decl.loc.start.line === node.loc.start.line) {
+              report(context, messages.propOnNewLine, 'propOnNewLine', {
+                node: decl,
+                fix(fixer) {
+                  const nodeTypeArguments = propsUtil.getTypeArguments(node);
+                  return fixer.replaceTextRange([(nodeTypeArguments || node.name).range[1], decl.range[0]], '\n');
+                },
+              });
+            }
+            return true;
+          });
+        } else if (
+          (configuration === 'never' && node.attributes.length > 0)
+          || (configuration === 'multiprop' && isMultilineJSX(node) && node.attributes.length <= 1)
+        ) {
+          const firstNode = node.attributes[0];
+          if (node.loc.start.line < firstNode.loc.start.line) {
+            report(context, messages.propOnSameLine, 'propOnSameLine', {
+              node: firstNode,
+              fix(fixer) {
+                return fixer.replaceTextRange([node.name.range[1], firstNode.range[0]], ' ');
+              },
+            });
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-fragments.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-fragments.d.ts","sourceRoot":"","sources":["jsx-fragments.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-fragments.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview Enforce shorthand or standard form for React fragments.
+ * @author Alex Zherdev
+ */
+
+'use strict';
+
+const elementType = require('jsx-ast-utils/elementType');
+const pragmaUtil = require('../util/pragma');
+const variableUtil = require('../util/variable');
+const testReactVersion = require('../util/version').testReactVersion;
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function replaceNode(source, node, text) {
+  return `${source.slice(0, node.range[0])}${text}${source.slice(node.range[1])}`;
+}
+
+const messages = {
+  fragmentsNotSupported: 'Fragments are only supported starting from React v16.2. Please disable the `react/jsx-fragments` rule in `eslint` settings or upgrade your version of React.',
+  preferPragma: 'Prefer {{react}}.{{fragment}} over fragment shorthand',
+  preferFragment: 'Prefer fragment shorthand over {{react}}.{{fragment}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce shorthand or standard form for React fragments',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-fragments'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      enum: ['syntax', 'element'],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || 'syntax';
+    const reactPragma = pragmaUtil.getFromContext(context);
+    const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
+    const openFragShort = '<>';
+    const closeFragShort = '</>';
+    const openFragLong = `<${reactPragma}.${fragmentPragma}>`;
+    const closeFragLong = `</${reactPragma}.${fragmentPragma}>`;
+
+    function reportOnReactVersion(node) {
+      if (!testReactVersion(context, '>= 16.2.0')) {
+        report(context, messages.fragmentsNotSupported, 'fragmentsNotSupported', {
+          node,
+        });
+        return true;
+      }
+
+      return false;
+    }
+
+    function getFixerToLong(jsxFragment) {
+      if (!jsxFragment.closingFragment || !jsxFragment.openingFragment) {
+        // the old TS parser crashes here
+        // TODO: FIXME: can we fake these two descriptors?
+        return null;
+      }
+      return function fix(fixer) {
+        let source = getText(context);
+        source = replaceNode(source, jsxFragment.closingFragment, closeFragLong);
+        source = replaceNode(source, jsxFragment.openingFragment, openFragLong);
+        const lengthDiff = openFragLong.length - getText(context, jsxFragment.openingFragment).length
+          + closeFragLong.length - getText(context, jsxFragment.closingFragment).length;
+        const range = jsxFragment.range;
+        return fixer.replaceTextRange(range, source.slice(range[0], range[1] + lengthDiff));
+      };
+    }
+
+    function getFixerToShort(jsxElement) {
+      return function fix(fixer) {
+        let source = getText(context);
+        let lengthDiff;
+        if (jsxElement.closingElement) {
+          source = replaceNode(source, jsxElement.closingElement, closeFragShort);
+          source = replaceNode(source, jsxElement.openingElement, openFragShort);
+          lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length
+            + getText(context, jsxElement.closingElement).length - closeFragShort.length;
+        } else {
+          source = replaceNode(source, jsxElement.openingElement, `${openFragShort}${closeFragShort}`);
+          lengthDiff = getText(context, jsxElement.openingElement).length - openFragShort.length
+            - closeFragShort.length;
+        }
+
+        const range = jsxElement.range;
+        return fixer.replaceTextRange(range, source.slice(range[0], range[1] - lengthDiff));
+      };
+    }
+
+    function refersToReactFragment(node, name) {
+      const variableInit = variableUtil.findVariableByName(context, node, name);
+      if (!variableInit) {
+        return false;
+      }
+
+      // const { Fragment } = React;
+      if (variableInit.type === 'Identifier' && variableInit.name === reactPragma) {
+        return true;
+      }
+
+      // const Fragment = React.Fragment;
+      if (
+        variableInit.type === 'MemberExpression'
+        && variableInit.object.type === 'Identifier'
+        && variableInit.object.name === reactPragma
+        && variableInit.property.type === 'Identifier'
+        && variableInit.property.name === fragmentPragma
+      ) {
+        return true;
+      }
+
+      // const { Fragment } = require('react');
+      if (
+        variableInit.callee
+        && variableInit.callee.name === 'require'
+        && variableInit.arguments
+        && variableInit.arguments[0]
+        && variableInit.arguments[0].value === 'react'
+      ) {
+        return true;
+      }
+
+      return false;
+    }
+
+    const jsxElements = [];
+    const fragmentNames = new Set([`${reactPragma}.${fragmentPragma}`]);
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXElement(node) {
+        jsxElements.push(node);
+      },
+
+      JSXFragment(node) {
+        if (reportOnReactVersion(node)) {
+          return;
+        }
+
+        if (configuration === 'element') {
+          report(context, messages.preferPragma, 'preferPragma', {
+            node,
+            data: {
+              react: reactPragma,
+              fragment: fragmentPragma,
+            },
+            fix: getFixerToLong(node),
+          });
+        }
+      },
+
+      ImportDeclaration(node) {
+        if (node.source && node.source.value === 'react') {
+          node.specifiers.forEach((spec) => {
+            if (
+              'imported' in spec
+              && spec.imported
+              && 'name' in spec.imported
+              && spec.imported.name === fragmentPragma
+            ) {
+              if (spec.local) {
+                fragmentNames.add(spec.local.name);
+              }
+            }
+          });
+        }
+      },
+
+      'Program:exit'() {
+        jsxElements.forEach((node) => {
+          const openingEl = node.openingElement;
+          const elName = elementType(openingEl);
+
+          if (fragmentNames.has(elName) || refersToReactFragment(node, elName)) {
+            if (reportOnReactVersion(node)) {
+              return;
+            }
+
+            const attrs = openingEl.attributes;
+            if (configuration === 'syntax' && !(attrs && attrs.length > 0)) {
+              report(context, messages.preferFragment, 'preferFragment', {
+                node,
+                data: {
+                  react: reactPragma,
+                  fragment: fragmentPragma,
+                },
+                fix: getFixerToShort(node),
+              });
+            }
+          }
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-handler-names.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-handler-names.d.ts","sourceRoot":"","sources":["jsx-handler-names.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-handler-names.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+/**
+ * @fileoverview Enforce event handler naming conventions in JSX
+ * @author Jake Marsh
+ */
+
+'use strict';
+
+const minimatch = require('minimatch');
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  badHandlerName: 'Handler function for {{propKey}} prop key must be a camelCase name beginning with \'{{handlerPrefix}}\' only',
+  badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}}\'',
+};
+
+function isPrefixDisabled(prefix) {
+  return prefix === false;
+}
+
+function isInlineHandler(node) {
+  return node.value.expression.type === 'ArrowFunctionExpression';
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce event handler naming conventions in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-handler-names'),
+    },
+
+    messages,
+
+    schema: [{
+      anyOf: [
+        {
+          type: 'object',
+          properties: {
+            eventHandlerPrefix: { type: 'string' },
+            eventHandlerPropPrefix: { type: 'string' },
+            checkLocalVariables: { type: 'boolean' },
+            checkInlineFunction: { type: 'boolean' },
+            ignoreComponentNames: {
+              type: 'array',
+              uniqueItems: true,
+              items: { type: 'string' },
+            },
+          },
+          additionalProperties: false,
+        }, {
+          type: 'object',
+          properties: {
+            eventHandlerPrefix: { type: 'string' },
+            eventHandlerPropPrefix: {
+              type: 'boolean',
+              enum: [false],
+            },
+            checkLocalVariables: { type: 'boolean' },
+            checkInlineFunction: { type: 'boolean' },
+            ignoreComponentNames: {
+              type: 'array',
+              uniqueItems: true,
+              items: { type: 'string' },
+            },
+          },
+          additionalProperties: false,
+        }, {
+          type: 'object',
+          properties: {
+            eventHandlerPrefix: {
+              type: 'boolean',
+              enum: [false],
+            },
+            eventHandlerPropPrefix: { type: 'string' },
+            checkLocalVariables: { type: 'boolean' },
+            checkInlineFunction: { type: 'boolean' },
+            ignoreComponentNames: {
+              type: 'array',
+              uniqueItems: true,
+              items: { type: 'string' },
+            },
+          },
+          additionalProperties: false,
+        }, {
+          type: 'object',
+          properties: {
+            checkLocalVariables: { type: 'boolean' },
+          },
+          additionalProperties: false,
+        }, {
+          type: 'object',
+          properties: {
+            checkInlineFunction: { type: 'boolean' },
+          },
+          additionalProperties: false,
+        },
+        {
+          type: 'object',
+          properties: {
+            ignoreComponentNames: {
+              type: 'array',
+              uniqueItems: true,
+              items: { type: 'string' },
+            },
+          },
+        },
+      ],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+
+    const eventHandlerPrefix = isPrefixDisabled(configuration.eventHandlerPrefix)
+      ? null
+      : configuration.eventHandlerPrefix || 'handle';
+    const eventHandlerPropPrefix = isPrefixDisabled(configuration.eventHandlerPropPrefix)
+      ? null
+      : configuration.eventHandlerPropPrefix || 'on';
+
+    const EVENT_HANDLER_REGEX = !eventHandlerPrefix
+      ? null
+      : new RegExp(`^((props\\.${eventHandlerPropPrefix || ''})|((.*\\.)?${eventHandlerPrefix}))[0-9]*[A-Z].*$`);
+    const PROP_EVENT_HANDLER_REGEX = !eventHandlerPropPrefix
+      ? null
+      : new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`);
+
+    const checkLocal = !!configuration.checkLocalVariables;
+
+    const checkInlineFunction = !!configuration.checkInlineFunction;
+
+    const ignoreComponentNames = configuration.ignoreComponentNames || [];
+
+    return {
+      JSXAttribute(node) {
+        const componentName = node.parent.name.name;
+
+        const isComponentNameIgnored = ignoreComponentNames.some((ignoredComponentNamePattern) => minimatch(
+          componentName,
+          ignoredComponentNamePattern
+        ));
+
+        if (
+          !node.value
+          || !node.value.expression
+          || (!checkInlineFunction && isInlineHandler(node))
+          || (
+            !checkLocal
+            && (isInlineHandler(node)
+              ? !node.value.expression.body.callee || !node.value.expression.body.callee.object
+              : !node.value.expression.object
+            )
+          )
+          || isComponentNameIgnored
+        ) {
+          return;
+        }
+
+        const propKey = typeof node.name === 'object' ? node.name.name : node.name;
+        const expression = node.value.expression;
+        const propValue = getText(
+          context,
+          checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression
+        ).replace(/\s*/g, '').replace(/^this\.|.*::/, '');
+
+        if (propKey === 'ref') {
+          return;
+        }
+
+        const propIsEventHandler = PROP_EVENT_HANDLER_REGEX && PROP_EVENT_HANDLER_REGEX.test(propKey);
+        const propFnIsNamedCorrectly = EVENT_HANDLER_REGEX && EVENT_HANDLER_REGEX.test(propValue);
+
+        if (
+          propIsEventHandler
+          && propFnIsNamedCorrectly !== null
+          && !propFnIsNamedCorrectly
+        ) {
+          report(context, messages.badHandlerName, 'badHandlerName', {
+            node,
+            data: {
+              propKey,
+              handlerPrefix: eventHandlerPrefix,
+            },
+          });
+        } else if (
+          propFnIsNamedCorrectly
+          && propIsEventHandler !== null
+          && !propIsEventHandler
+        ) {
+          report(context, messages.badPropKey, 'badPropKey', {
+            node,
+            data: {
+              propValue,
+              handlerPropPrefix: eventHandlerPropPrefix,
+            },
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-indent-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-indent-props.d.ts","sourceRoot":"","sources":["jsx-indent-props.js"],"names":[],"mappings":"wBA+CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,218 @@
+/**
+ * @fileoverview Validate props indentation in JSX
+ * @author Yannick Croissant
+
+ * This rule has been ported and modified from eslint and nodeca.
+ * @author Vitaly Puzrin
+ * @author Gyandeep Singh
+ * @copyright 2015 Vitaly Puzrin. All rights reserved.
+ * @copyright 2015 Gyandeep Singh. All rights reserved.
+ Copyright (C) 2014 by Vitaly Puzrin
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the 'Software'), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+'use strict';
+
+const repeat = require('string.prototype.repeat');
+
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const reportC = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce props indentation in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-indent-props'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      anyOf: [{
+        enum: ['tab', 'first'],
+      }, {
+        type: 'integer',
+      }, {
+        type: 'object',
+        properties: {
+          indentMode: {
+            anyOf: [{
+              enum: ['tab', 'first'],
+            }, {
+              type: 'integer',
+            }],
+          },
+          ignoreTernaryOperator: {
+            type: 'boolean',
+          },
+        },
+      }],
+    }],
+  },
+
+  create(context) {
+    const extraColumnStart = 0;
+    let indentType = 'space';
+    /** @type {number|'first'} */
+    let indentSize = 4;
+    const line = {
+      isUsingOperator: false,
+      currentOperator: false,
+    };
+    let ignoreTernaryOperator = false;
+
+    if (context.options.length) {
+      const isConfigObject = typeof context.options[0] === 'object';
+      const indentMode = isConfigObject
+        ? context.options[0].indentMode
+        : context.options[0];
+
+      if (indentMode === 'first') {
+        indentSize = 'first';
+        indentType = 'space';
+      } else if (indentMode === 'tab') {
+        indentSize = 1;
+        indentType = 'tab';
+      } else if (typeof indentMode === 'number') {
+        indentSize = indentMode;
+        indentType = 'space';
+      }
+
+      if (isConfigObject && context.options[0].ignoreTernaryOperator) {
+        ignoreTernaryOperator = true;
+      }
+    }
+
+    /**
+     * Reports a given indent violation and properly pluralizes the message
+     * @param {ASTNode} node Node violating the indent rule
+     * @param {number} needed Expected indentation character count
+     * @param {number} gotten Indentation character count in the actual node/code
+     */
+    function report(node, needed, gotten) {
+      const msgContext = {
+        needed,
+        type: indentType,
+        characters: needed === 1 ? 'character' : 'characters',
+        gotten,
+      };
+
+      reportC(context, messages.wrongIndent, 'wrongIndent', {
+        node,
+        data: msgContext,
+        fix(fixer) {
+          return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]],
+            repeat(indentType === 'space' ? ' ' : '\t', needed)
+          );
+        },
+      });
+    }
+
+    /**
+     * Get node indent
+     * @param {ASTNode} node Node to examine
+     * @return {number} Indent
+     */
+    function getNodeIndent(node) {
+      let src = getText(context, node, node.loc.start.column + extraColumnStart);
+      const lines = src.split('\n');
+      src = lines[0];
+
+      let regExp;
+      if (indentType === 'space') {
+        regExp = /^[ ]+/;
+      } else {
+        regExp = /^[\t]+/;
+      }
+
+      const indent = regExp.exec(src);
+      const useOperator = /^([ ]|[\t])*[:]/.test(src) || /^([ ]|[\t])*[?]/.test(src);
+      const useBracket = /[<]/.test(src);
+
+      line.currentOperator = false;
+      if (useOperator) {
+        line.isUsingOperator = true;
+        line.currentOperator = true;
+      } else if (useBracket) {
+        line.isUsingOperator = false;
+      }
+
+      return indent ? indent[0].length : 0;
+    }
+
+    /**
+     * Check indent for nodes list
+     * @param {ASTNode[]} nodes list of node objects
+     * @param {number} indent needed indent
+     */
+    function checkNodesIndent(nodes, indent) {
+      let nestedIndent = indent;
+      nodes.forEach((node) => {
+        const nodeIndent = getNodeIndent(node);
+        if (
+          line.isUsingOperator
+          && !line.currentOperator
+          && indentSize !== 'first'
+          && !ignoreTernaryOperator
+        ) {
+          nestedIndent += indentSize;
+          line.isUsingOperator = false;
+        }
+        if (
+          node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression'
+          && nodeIndent !== nestedIndent && astUtil.isNodeFirstInLine(context, node)
+        ) {
+          report(node, nestedIndent, nodeIndent);
+        }
+      });
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        if (!node.attributes.length) {
+          return;
+        }
+        let propIndent;
+        if (indentSize === 'first') {
+          const firstPropNode = node.attributes[0];
+          propIndent = firstPropNode.loc.start.column;
+        } else {
+          const elementIndent = getNodeIndent(node);
+          propIndent = elementIndent + indentSize;
+        }
+        checkNodesIndent(node.attributes, propIndent);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-indent.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-indent.d.ts","sourceRoot":"","sources":["jsx-indent.js"],"names":[],"mappings":"wBAoDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-indent.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,447 @@
+/**
+ * @fileoverview Validate JSX indentation
+ * @author Yannick Croissant
+
+ * This rule has been ported and modified from eslint and nodeca.
+ * @author Vitaly Puzrin
+ * @author Gyandeep Singh
+ * @copyright 2015 Vitaly Puzrin. All rights reserved.
+ * @copyright 2015 Gyandeep Singh. All rights reserved.
+ Copyright (C) 2014 by Vitaly Puzrin
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the 'Software'), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+'use strict';
+
+const matchAll = require('string.prototype.matchall');
+const repeat = require('string.prototype.repeat');
+
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const reportC = require('../util/report');
+const jsxUtil = require('../util/jsx');
+const eslintUtil = require('../util/eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce JSX indentation',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-indent'),
+    },
+    fixable: 'whitespace',
+
+    messages,
+
+    schema: [{
+      anyOf: [{
+        enum: ['tab'],
+      }, {
+        type: 'integer',
+      }],
+    }, {
+      type: 'object',
+      properties: {
+        checkAttributes: {
+          type: 'boolean',
+        },
+        indentLogicalExpressions: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const extraColumnStart = 0;
+    let indentType = 'space';
+    let indentSize = 4;
+
+    if (context.options.length) {
+      if (context.options[0] === 'tab') {
+        indentSize = 1;
+        indentType = 'tab';
+      } else if (typeof context.options[0] === 'number') {
+        indentSize = context.options[0];
+        indentType = 'space';
+      }
+    }
+
+    const indentChar = indentType === 'space' ? ' ' : '\t';
+    const options = context.options[1] || {};
+    const checkAttributes = options.checkAttributes || false;
+    const indentLogicalExpressions = options.indentLogicalExpressions || false;
+
+    /**
+     * Responsible for fixing the indentation issue fix
+     * @param {ASTNode} node Node violating the indent rule
+     * @param {number} needed Expected indentation character count
+     * @returns {Function} function to be executed by the fixer
+     * @private
+     */
+    function getFixerFunction(node, needed) {
+      const indent = repeat(indentChar, needed);
+
+      if (node.type === 'JSXText' || node.type === 'Literal') {
+        return function fix(fixer) {
+          const regExp = /\n[\t ]*(\S)/g;
+          const fixedText = node.raw.replace(regExp, (match, p1) => `\n${indent}${p1}`);
+          return fixer.replaceText(node, fixedText);
+        };
+      }
+
+      if (node.type === 'ReturnStatement') {
+        const raw = getText(context, node);
+        const lines = raw.split('\n');
+        if (lines.length > 1) {
+          return function fix(fixer) {
+            const lastLineStart = raw.lastIndexOf('\n');
+            const lastLine = raw.slice(lastLineStart).replace(/^\n[\t ]*(\S)/, (match, p1) => `\n${indent}${p1}`);
+            return fixer.replaceTextRange(
+              [node.range[0] + lastLineStart, node.range[1]],
+              lastLine
+            );
+          };
+        }
+      }
+
+      return function fix(fixer) {
+        return fixer.replaceTextRange(
+          [node.range[0] - node.loc.start.column, node.range[0]],
+          indent
+        );
+      };
+    }
+
+    /**
+     * Reports a given indent violation and properly pluralizes the message
+     * @param {ASTNode} node Node violating the indent rule
+     * @param {number} needed Expected indentation character count
+     * @param {number} gotten Indentation character count in the actual node/code
+     * @param {Object} [loc] Error line and column location
+     */
+    function report(node, needed, gotten, loc) {
+      const msgContext = {
+        needed,
+        type: indentType,
+        characters: needed === 1 ? 'character' : 'characters',
+        gotten,
+      };
+
+      reportC(context, messages.wrongIndent, 'wrongIndent', Object.assign({
+        node,
+        data: msgContext,
+        fix: getFixerFunction(node, needed),
+      }, loc && { loc }));
+    }
+
+    /**
+     * Get node indent
+     * @param {ASTNode} node Node to examine
+     * @param {boolean} [byLastLine] get indent of node's last line
+     * @param {boolean} [excludeCommas] skip comma on start of line
+     * @return {number} Indent
+     */
+    function getNodeIndent(node, byLastLine, excludeCommas) {
+      let src = getText(context, node, node.loc.start.column + extraColumnStart);
+      const lines = src.split('\n');
+      if (byLastLine) {
+        src = lines[lines.length - 1];
+      } else {
+        src = lines[0];
+      }
+
+      const skip = excludeCommas ? ',' : '';
+
+      let regExp;
+      if (indentType === 'space') {
+        regExp = new RegExp(`^[ ${skip}]+`);
+      } else {
+        regExp = new RegExp(`^[\t${skip}]+`);
+      }
+
+      const indent = regExp.exec(src);
+      return indent ? indent[0].length : 0;
+    }
+
+    /**
+     * Check if the node is the right member of a logical expression
+     * @param {ASTNode} node The node to check
+     * @return {boolean} true if its the case, false if not
+     */
+    function isRightInLogicalExp(node) {
+      return (
+        node.parent
+        && node.parent.parent
+        && node.parent.parent.type === 'LogicalExpression'
+        && node.parent.parent.right === node.parent
+        && !indentLogicalExpressions
+      );
+    }
+
+    /**
+     * Check if the node is the alternate member of a conditional expression
+     * @param {ASTNode} node The node to check
+     * @return {boolean} true if its the case, false if not
+     */
+    function isAlternateInConditionalExp(node) {
+      return (
+        node.parent
+        && node.parent.parent
+        && node.parent.parent.type === 'ConditionalExpression'
+        && node.parent.parent.alternate === node.parent
+        && getSourceCode(context).getTokenBefore(node).value !== '('
+      );
+    }
+
+    /**
+     * Check if the node is within a DoExpression block but not the first expression (which need to be indented)
+     * @param {ASTNode} node The node to check
+     * @return {boolean} true if its the case, false if not
+     */
+    function isSecondOrSubsequentExpWithinDoExp(node) {
+      /*
+        It returns true when node.parent.parent.parent.parent matches:
+
+        DoExpression({
+          ...,
+          body: BlockStatement({
+            ...,
+            body: [
+              ...,  // 1-n times
+              ExpressionStatement({
+                ...,
+                expression: JSXElement({
+                  ...,
+                  openingElement: JSXOpeningElement()  // the node
+                })
+              }),
+              ...  // 0-n times
+            ]
+          })
+        })
+
+        except:
+
+        DoExpression({
+          ...,
+          body: BlockStatement({
+            ...,
+            body: [
+              ExpressionStatement({
+                ...,
+                expression: JSXElement({
+                  ...,
+                  openingElement: JSXOpeningElement()  // the node
+                })
+              }),
+              ...  // 0-n times
+            ]
+          })
+        })
+      */
+      const isInExpStmt = (
+        node.parent
+        && node.parent.parent
+        && node.parent.parent.type === 'ExpressionStatement'
+      );
+      if (!isInExpStmt) {
+        return false;
+      }
+
+      const expStmt = node.parent.parent;
+      const isInBlockStmtWithinDoExp = (
+        expStmt.parent
+        && expStmt.parent.type === 'BlockStatement'
+        && expStmt.parent.parent
+        && expStmt.parent.parent.type === 'DoExpression'
+      );
+      if (!isInBlockStmtWithinDoExp) {
+        return false;
+      }
+
+      const blockStmt = expStmt.parent;
+      const blockStmtFirstExp = blockStmt.body[0];
+      return !(blockStmtFirstExp === expStmt);
+    }
+
+    /**
+     * Check indent for nodes list
+     * @param {ASTNode} node The node to check
+     * @param {number} indent needed indent
+     * @param {boolean} [excludeCommas] skip comma on start of line
+     */
+    function checkNodesIndent(node, indent, excludeCommas) {
+      const nodeIndent = getNodeIndent(node, false, excludeCommas);
+      const isCorrectRightInLogicalExp = isRightInLogicalExp(node) && (nodeIndent - indent) === indentSize;
+      const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0;
+      if (
+        nodeIndent !== indent
+        && astUtil.isNodeFirstInLine(context, node)
+        && !isCorrectRightInLogicalExp
+        && !isCorrectAlternateInCondExp
+      ) {
+        report(node, indent, nodeIndent);
+      }
+    }
+
+    /**
+     * Check indent for Literal Node or JSXText Node
+     * @param {ASTNode} node The node to check
+     * @param {number} indent needed indent
+     */
+    function checkLiteralNodeIndent(node, indent) {
+      const value = node.value;
+      const regExp = indentType === 'space' ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g;
+      const nodeIndentsPerLine = Array.from(
+        matchAll(String(value), regExp),
+        (match) => (match[1] ? match[1].length : 0)
+      );
+      const hasFirstInLineNode = nodeIndentsPerLine.length > 0;
+      if (
+        hasFirstInLineNode
+        && !nodeIndentsPerLine.every((actualIndent) => actualIndent === indent)
+      ) {
+        nodeIndentsPerLine.forEach((nodeIndent) => {
+          report(node, indent, nodeIndent);
+        });
+      }
+    }
+
+    function handleOpeningElement(node) {
+      const sourceCode = getSourceCode(context);
+      let prevToken = sourceCode.getTokenBefore(node);
+      if (!prevToken) {
+        return;
+      }
+      // Use the parent in a list or an array
+      if (prevToken.type === 'JSXText' || ((prevToken.type === 'Punctuator') && prevToken.value === ',')) {
+        prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
+        prevToken = prevToken.type === 'Literal' || prevToken.type === 'JSXText' ? prevToken.parent : prevToken;
+      // Use the first non-punctuator token in a conditional expression
+      } else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
+        do {
+          prevToken = sourceCode.getTokenBefore(prevToken);
+        } while (prevToken.type === 'Punctuator' && prevToken.value !== '/');
+        prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
+        while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
+          prevToken = prevToken.parent;
+        }
+      }
+      prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
+      const parentElementIndent = getNodeIndent(prevToken);
+      const indent = (
+        prevToken.loc.start.line === node.loc.start.line
+        || isRightInLogicalExp(node)
+        || isAlternateInConditionalExp(node)
+        || isSecondOrSubsequentExpWithinDoExp(node)
+      ) ? 0 : indentSize;
+      checkNodesIndent(node, parentElementIndent + indent);
+    }
+
+    function handleClosingElement(node) {
+      if (!node.parent) {
+        return;
+      }
+      const peerElementIndent = getNodeIndent(node.parent.openingElement || node.parent.openingFragment);
+      checkNodesIndent(node, peerElementIndent);
+    }
+
+    function handleAttribute(node) {
+      if (!checkAttributes || (!node.value || node.value.type !== 'JSXExpressionContainer')) {
+        return;
+      }
+      const nameIndent = getNodeIndent(node.name);
+      const lastToken = getSourceCode(context).getLastToken(node.value);
+      const firstInLine = astUtil.getFirstNodeInLine(context, lastToken);
+      const indent = node.name.loc.start.line === firstInLine.loc.start.line ? 0 : nameIndent;
+      checkNodesIndent(firstInLine, indent);
+    }
+
+    function handleLiteral(node) {
+      if (!node.parent) {
+        return;
+      }
+      if (node.parent.type !== 'JSXElement' && node.parent.type !== 'JSXFragment') {
+        return;
+      }
+      const parentNodeIndent = getNodeIndent(node.parent);
+      checkLiteralNodeIndent(node, parentNodeIndent + indentSize);
+    }
+
+    return {
+      JSXOpeningElement: handleOpeningElement,
+      JSXOpeningFragment: handleOpeningElement,
+      JSXClosingElement: handleClosingElement,
+      JSXClosingFragment: handleClosingElement,
+      JSXAttribute: handleAttribute,
+      JSXExpressionContainer(node) {
+        if (!node.parent) {
+          return;
+        }
+        const parentNodeIndent = getNodeIndent(node.parent);
+        checkNodesIndent(node, parentNodeIndent + indentSize);
+      },
+      Literal: handleLiteral,
+      JSXText: handleLiteral,
+
+      ReturnStatement(node) {
+        if (
+          !node.parent
+          || !jsxUtil.isJSX(node.argument)
+        ) {
+          return;
+        }
+
+        let fn = node.parent;
+        while (fn && fn.type !== 'FunctionDeclaration' && fn.type !== 'FunctionExpression') {
+          fn = fn.parent;
+        }
+        if (
+          !fn
+          || !jsxUtil.isReturningJSX(context, node, true)
+        ) {
+          return;
+        }
+
+        const openingIndent = getNodeIndent(node);
+        const closingIndent = getNodeIndent(node, true);
+
+        if (openingIndent !== closingIndent) {
+          report(node, openingIndent, closingIndent);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-key.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-key.d.ts","sourceRoot":"","sources":["jsx-key.js"],"names":[],"mappings":"wBAmCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,303 @@
+/**
+ * @fileoverview Report missing `key` props in iterators/collection literals.
+ * @author Ben Mosher
+ */
+
+'use strict';
+
+const hasProp = require('jsx-ast-utils/hasProp');
+const propName = require('jsx-ast-utils/propName');
+const values = require('object.values');
+const docsUrl = require('../util/docsUrl');
+const pragmaUtil = require('../util/pragma');
+const report = require('../util/report');
+const astUtil = require('../util/ast');
+const getText = require('../util/eslint').getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const defaultOptions = {
+  checkFragmentShorthand: false,
+  checkKeyMustBeforeSpread: false,
+  warnOnDuplicates: false,
+};
+
+const messages = {
+  missingIterKey: 'Missing "key" prop for element in iterator',
+  missingIterKeyUsePrag: 'Missing "key" prop for element in iterator. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead',
+  missingArrayKey: 'Missing "key" prop for element in array',
+  missingArrayKeyUsePrag: 'Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead',
+  keyBeforeSpread: '`key` prop must be placed before any `{...spread}, to avoid conflicting with React’s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`',
+  nonUniqueKeys: '`key` prop must be unique',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow missing `key` props in iterators/collection literals',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('jsx-key'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        checkFragmentShorthand: {
+          type: 'boolean',
+          default: defaultOptions.checkFragmentShorthand,
+        },
+        checkKeyMustBeforeSpread: {
+          type: 'boolean',
+          default: defaultOptions.checkKeyMustBeforeSpread,
+        },
+        warnOnDuplicates: {
+          type: 'boolean',
+          default: defaultOptions.warnOnDuplicates,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const options = Object.assign({}, defaultOptions, context.options[0]);
+    const checkFragmentShorthand = options.checkFragmentShorthand;
+    const checkKeyMustBeforeSpread = options.checkKeyMustBeforeSpread;
+    const warnOnDuplicates = options.warnOnDuplicates;
+    const reactPragma = pragmaUtil.getFromContext(context);
+    const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
+
+    function isKeyAfterSpread(attributes) {
+      let hasFoundSpread = false;
+      return attributes.some((attribute) => {
+        if (attribute.type === 'JSXSpreadAttribute') {
+          hasFoundSpread = true;
+          return false;
+        }
+        if (attribute.type !== 'JSXAttribute') {
+          return false;
+        }
+        return hasFoundSpread && propName(attribute) === 'key';
+      });
+    }
+
+    function checkIteratorElement(node) {
+      if (node.type === 'JSXElement') {
+        if (!hasProp(node.openingElement.attributes, 'key')) {
+          report(context, messages.missingIterKey, 'missingIterKey', { node });
+        } else {
+          const attrs = node.openingElement.attributes;
+
+          if (checkKeyMustBeforeSpread && isKeyAfterSpread(attrs)) {
+            report(context, messages.keyBeforeSpread, 'keyBeforeSpread', { node });
+          }
+        }
+      } else if (checkFragmentShorthand && node.type === 'JSXFragment') {
+        report(context, messages.missingIterKeyUsePrag, 'missingIterKeyUsePrag', {
+          node,
+          data: {
+            reactPrag: reactPragma,
+            fragPrag: fragmentPragma,
+          },
+        });
+      }
+    }
+
+    function getReturnStatements(node) {
+      const returnStatements = arguments[1] || [];
+      if (node.type === 'IfStatement') {
+        if (node.consequent) {
+          getReturnStatements(node.consequent, returnStatements);
+        }
+        if (node.alternate) {
+          getReturnStatements(node.alternate, returnStatements);
+        }
+      } else if (node.type === 'ReturnStatement') {
+        returnStatements.push(node);
+      } else if (Array.isArray(node.body)) {
+        node.body.forEach((item) => {
+          if (item.type === 'IfStatement') {
+            getReturnStatements(item, returnStatements);
+          }
+
+          if (item.type === 'ReturnStatement') {
+            returnStatements.push(item);
+          }
+        });
+      }
+
+      return returnStatements;
+    }
+
+    /**
+     * Checks if the given node is a function expression or arrow function,
+     * and checks if there is a missing key prop in return statement's arguments
+     * @param {ASTNode} node
+     */
+    function checkFunctionsBlockStatement(node) {
+      if (astUtil.isFunctionLikeExpression(node)) {
+        if (node.body.type === 'BlockStatement') {
+          getReturnStatements(node.body)
+            .filter((returnStatement) => returnStatement && returnStatement.argument)
+            .forEach((returnStatement) => {
+              checkIteratorElement(returnStatement.argument);
+            });
+        }
+      }
+    }
+
+    /**
+     * Checks if the given node is an arrow function that has an JSX Element or JSX Fragment in its body,
+     * and the JSX is missing a key prop
+     * @param {ASTNode} node
+     */
+    function checkArrowFunctionWithJSX(node) {
+      const isArrFn = node && node.type === 'ArrowFunctionExpression';
+      const shouldCheckNode = (n) => n && (n.type === 'JSXElement' || n.type === 'JSXFragment');
+      if (isArrFn && shouldCheckNode(node.body)) {
+        checkIteratorElement(node.body);
+      }
+      if (node.body.type === 'ConditionalExpression') {
+        if (shouldCheckNode(node.body.consequent)) {
+          checkIteratorElement(node.body.consequent);
+        }
+        if (shouldCheckNode(node.body.alternate)) {
+          checkIteratorElement(node.body.alternate);
+        }
+      } else if (node.body.type === 'LogicalExpression' && shouldCheckNode(node.body.right)) {
+        checkIteratorElement(node.body.right);
+      }
+    }
+
+    const childrenToArraySelector = `:matches(
+      CallExpression
+        [callee.object.object.name=${reactPragma}]
+        [callee.object.property.name=Children]
+        [callee.property.name=toArray],
+      CallExpression
+        [callee.object.name=Children]
+        [callee.property.name=toArray]
+    )`.replace(/\s/g, '');
+    let isWithinChildrenToArray = false;
+
+    const seen = new WeakSet();
+
+    return {
+      [childrenToArraySelector]() {
+        isWithinChildrenToArray = true;
+      },
+
+      [`${childrenToArraySelector}:exit`]() {
+        isWithinChildrenToArray = false;
+      },
+
+      'ArrayExpression, JSXElement > JSXElement'(node) {
+        if (isWithinChildrenToArray) {
+          return;
+        }
+
+        const jsx = (node.type === 'ArrayExpression' ? node.elements : node.parent.children).filter((x) => x && x.type === 'JSXElement');
+        if (jsx.length === 0) {
+          return;
+        }
+
+        const map = {};
+        jsx.forEach((element) => {
+          const attrs = element.openingElement.attributes;
+          const keys = attrs.filter((x) => x.name && x.name.name === 'key');
+
+          if (keys.length === 0) {
+            if (node.type === 'ArrayExpression') {
+              report(context, messages.missingArrayKey, 'missingArrayKey', {
+                node: element,
+              });
+            }
+          } else {
+            keys.forEach((attr) => {
+              const value = getText(context, attr.value);
+              if (!map[value]) { map[value] = []; }
+              map[value].push(attr);
+
+              if (checkKeyMustBeforeSpread && isKeyAfterSpread(attrs)) {
+                report(context, messages.keyBeforeSpread, 'keyBeforeSpread', {
+                  node: node.type === 'ArrayExpression' ? node : node.parent,
+                });
+              }
+            });
+          }
+        });
+
+        if (warnOnDuplicates) {
+          values(map).filter((v) => v.length > 1).forEach((v) => {
+            v.forEach((n) => {
+              if (!seen.has(n)) {
+                seen.add(n);
+                report(context, messages.nonUniqueKeys, 'nonUniqueKeys', {
+                  node: n,
+                });
+              }
+            });
+          });
+        }
+      },
+
+      JSXFragment(node) {
+        if (!checkFragmentShorthand || isWithinChildrenToArray) {
+          return;
+        }
+
+        if (node.parent.type === 'ArrayExpression') {
+          report(context, messages.missingArrayKeyUsePrag, 'missingArrayKeyUsePrag', {
+            node,
+            data: {
+              reactPrag: reactPragma,
+              fragPrag: fragmentPragma,
+            },
+          });
+        }
+      },
+
+      // Array.prototype.map
+      // eslint-disable-next-line no-multi-str
+      'CallExpression[callee.type="MemberExpression"][callee.property.name="map"],\
+       CallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"],\
+       OptionalCallExpression[callee.type="MemberExpression"][callee.property.name="map"],\
+       OptionalCallExpression[callee.type="OptionalMemberExpression"][callee.property.name="map"]'(node) {
+        if (isWithinChildrenToArray) {
+          return;
+        }
+
+        const fn = node.arguments.length > 0 && node.arguments[0];
+        if (!fn || !astUtil.isFunctionLikeExpression(fn)) {
+          return;
+        }
+
+        checkArrowFunctionWithJSX(fn);
+
+        checkFunctionsBlockStatement(fn);
+      },
+
+      // Array.from
+      'CallExpression[callee.type="MemberExpression"][callee.property.name="from"]'(node) {
+        if (isWithinChildrenToArray) {
+          return;
+        }
+
+        const fn = node.arguments.length > 1 && node.arguments[1];
+        if (!astUtil.isFunctionLikeExpression(fn)) {
+          return;
+        }
+
+        checkArrowFunctionWithJSX(fn);
+
+        checkFunctionsBlockStatement(fn);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-max-depth.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-max-depth.d.ts","sourceRoot":"","sources":["jsx-max-depth.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-depth.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,162 @@
+/**
+ * @fileoverview Validate JSX maximum depth
+ * @author Chris<wfsr@foxmail.com>
+ */
+
+'use strict';
+
+const has = require('hasown');
+const includes = require('array-includes');
+const variableUtil = require('../util/variable');
+const jsxUtil = require('../util/jsx');
+const docsUrl = require('../util/docsUrl');
+const reportC = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  wrongDepth: 'Expected the depth of nested jsx elements to be <= {{needed}}, but found {{found}}.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce JSX maximum depth',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-max-depth'),
+    },
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          max: {
+            type: 'integer',
+            minimum: 0,
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
+  },
+  create(context) {
+    const DEFAULT_DEPTH = 2;
+
+    const option = context.options[0] || {};
+    const maxDepth = has(option, 'max') ? option.max : DEFAULT_DEPTH;
+
+    function isExpression(node) {
+      return node.type === 'JSXExpressionContainer';
+    }
+
+    function hasJSX(node) {
+      return jsxUtil.isJSX(node) || (isExpression(node) && jsxUtil.isJSX(node.expression));
+    }
+
+    function isLeaf(node) {
+      const children = node.children;
+
+      return !children || children.length === 0 || !children.some(hasJSX);
+    }
+
+    function getDepth(node) {
+      let count = 0;
+
+      while (jsxUtil.isJSX(node.parent) || isExpression(node.parent)) {
+        node = node.parent;
+        if (jsxUtil.isJSX(node)) {
+          count += 1;
+        }
+      }
+
+      return count;
+    }
+
+    function report(node, depth) {
+      reportC(context, messages.wrongDepth, 'wrongDepth', {
+        node,
+        data: {
+          found: depth,
+          needed: maxDepth,
+        },
+      });
+    }
+
+    function findJSXElementOrFragment(startNode, name, previousReferences) {
+      function find(refs, prevRefs) {
+        for (let i = refs.length - 1; i >= 0; i--) {
+          if (typeof refs[i].writeExpr !== 'undefined') {
+            const writeExpr = refs[i].writeExpr;
+
+            return (jsxUtil.isJSX(writeExpr)
+              && writeExpr)
+              || ((writeExpr && writeExpr.type === 'Identifier')
+              && findJSXElementOrFragment(startNode, writeExpr.name, prevRefs));
+          }
+        }
+
+        return null;
+      }
+
+      const variable = variableUtil.getVariableFromContext(context, startNode, name);
+      if (variable && variable.references) {
+        const containDuplicates = previousReferences.some((ref) => includes(variable.references, ref));
+
+        // Prevent getting stuck in circular references
+        if (containDuplicates) {
+          return false;
+        }
+
+        return find(variable.references, previousReferences.concat(variable.references));
+      }
+
+      return false;
+    }
+
+    function checkDescendant(baseDepth, children) {
+      baseDepth += 1;
+      (children || []).filter((node) => hasJSX(node)).forEach((node) => {
+        if (baseDepth > maxDepth) {
+          report(node, baseDepth);
+        } else if (!isLeaf(node)) {
+          checkDescendant(baseDepth, node.children);
+        }
+      });
+    }
+
+    function handleJSX(node) {
+      if (!isLeaf(node)) {
+        return;
+      }
+
+      const depth = getDepth(node);
+      if (depth > maxDepth) {
+        report(node, depth);
+      }
+    }
+
+    return {
+      JSXElement: handleJSX,
+      JSXFragment: handleJSX,
+
+      JSXExpressionContainer(node) {
+        if (node.expression.type !== 'Identifier') {
+          return;
+        }
+
+        const element = findJSXElementOrFragment(node, node.expression.name, []);
+
+        if (element) {
+          const baseDepth = getDepth(node);
+          checkDescendant(baseDepth, element.children);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-max-props-per-line.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-max-props-per-line.d.ts","sourceRoot":"","sources":["jsx-max-props-per-line.js"],"names":[],"mappings":"wBA0BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-max-props-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,155 @@
+/**
+ * @fileoverview Limit maximum of props on a single line in JSX
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const report = require('../util/report');
+
+function getPropName(context, propNode) {
+  if (propNode.type === 'JSXSpreadAttribute') {
+    return getText(context, propNode.argument);
+  }
+  return propNode.name.name;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  newLine: 'Prop `{{prop}}` must be placed on a new line',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce maximum of props on a single line in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-max-props-per-line'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      anyOf: [{
+        type: 'object',
+        properties: {
+          maximum: {
+            type: 'object',
+            properties: {
+              single: {
+                type: 'integer',
+                minimum: 1,
+              },
+              multi: {
+                type: 'integer',
+                minimum: 1,
+              },
+            },
+          },
+        },
+        additionalProperties: false,
+      }, {
+        type: 'object',
+        properties: {
+          maximum: {
+            type: 'number',
+            minimum: 1,
+          },
+          when: {
+            type: 'string',
+            enum: ['always', 'multiline'],
+          },
+        },
+        additionalProperties: false,
+      }],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const maximum = configuration.maximum || 1;
+
+    const maxConfig = typeof maximum === 'number'
+      ? {
+        single: configuration.when === 'multiline' ? Infinity : maximum,
+        multi: maximum,
+      }
+      : {
+        single: maximum.single || Infinity,
+        multi: maximum.multi || Infinity,
+      };
+
+    function generateFixFunction(line, max) {
+      const output = [];
+      const front = line[0].range[0];
+      const back = line[line.length - 1].range[1];
+
+      for (let i = 0; i < line.length; i += max) {
+        const nodes = line.slice(i, i + max);
+        output.push(nodes.reduce((prev, curr) => {
+          if (prev === '') {
+            return getText(context, curr);
+          }
+          return `${prev} ${getText(context, curr)}`;
+        }, ''));
+      }
+
+      const code = output.join('\n');
+
+      return function fix(fixer) {
+        return fixer.replaceTextRange([front, back], code);
+      };
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        if (!node.attributes.length) {
+          return;
+        }
+
+        const isSingleLineTag = node.loc.start.line === node.loc.end.line;
+
+        if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) {
+          return;
+        }
+
+        const firstProp = node.attributes[0];
+        const linePartitionedProps = [[firstProp]];
+
+        node.attributes.reduce((last, decl) => {
+          if (last.loc.end.line === decl.loc.start.line) {
+            linePartitionedProps[linePartitionedProps.length - 1].push(decl);
+          } else {
+            linePartitionedProps.push([decl]);
+          }
+          return decl;
+        });
+
+        linePartitionedProps.forEach((propsInLine) => {
+          const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line
+            ? maxConfig.single
+            : maxConfig.multi;
+
+          if (propsInLine.length > maxPropsCountPerLine) {
+            const name = getPropName(context, propsInLine[maxPropsCountPerLine]);
+            report(context, messages.newLine, 'newLine', {
+              node: propsInLine[maxPropsCountPerLine],
+              data: {
+                prop: name,
+              },
+              fix: generateFixFunction(propsInLine, maxPropsCountPerLine),
+            });
+          }
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-newline.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-newline.d.ts","sourceRoot":"","sources":["jsx-newline.js"],"names":[],"mappings":"wBA0BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-newline.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,166 @@
+/**
+ * @fileoverview Require or prevent a new line after jsx elements and expressions.
+ * @author Johnny Zabala
+ * @author Joseph Stiles
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  require: 'JSX element should start in a new line',
+  prevent: 'JSX element should not start in a new line',
+  allowMultilines: 'Multiline JSX elements should start in a new line',
+};
+
+function isMultilined(node) {
+  return node && node.loc.start.line !== node.loc.end.line;
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Require or prevent a new line after jsx elements and expressions.',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-newline'),
+    },
+    fixable: 'code',
+
+    messages,
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          prevent: {
+            default: false,
+            type: 'boolean',
+          },
+          allowMultilines: {
+            default: false,
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+        if: {
+          properties: {
+            allowMultilines: {
+              const: true,
+            },
+          },
+        },
+        then: {
+          properties: {
+            prevent: {
+              const: true,
+            },
+          },
+          required: [
+            'prevent',
+          ],
+        },
+      },
+    ],
+  },
+  create(context) {
+    const jsxElementParents = new Set();
+
+    function isBlockCommentInCurlyBraces(element) {
+      const elementRawValue = getText(context, element);
+      return /^\s*{\/\*/.test(elementRawValue);
+    }
+
+    function isNonBlockComment(element) {
+      return !isBlockCommentInCurlyBraces(element) && (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer');
+    }
+
+    return {
+      'Program:exit'() {
+        jsxElementParents.forEach((parent) => {
+          parent.children.forEach((element, index, elements) => {
+            if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
+              const configuration = context.options[0] || {};
+              const prevent = configuration.prevent || false;
+              const allowMultilines = configuration.allowMultilines || false;
+
+              const firstAdjacentSibling = elements[index + 1];
+              const secondAdjacentSibling = elements[index + 2];
+
+              const hasSibling = firstAdjacentSibling
+              && secondAdjacentSibling
+              && (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText');
+
+              if (!hasSibling) return;
+
+              // Check adjacent sibling has the proper amount of newlines
+              const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
+
+              if (isBlockCommentInCurlyBraces(element)) return;
+              if (
+                allowMultilines
+                && (
+                  isMultilined(element)
+                  || isMultilined(elements.slice(index + 2).find(isNonBlockComment))
+                )
+              ) {
+                if (!isWithoutNewLine) return;
+
+                const regex = /(\n)(?!.*\1)/g;
+                const replacement = '\n\n';
+                const messageId = 'allowMultilines';
+
+                report(context, messages[messageId], messageId, {
+                  node: secondAdjacentSibling,
+                  fix(fixer) {
+                    return fixer.replaceText(
+                      firstAdjacentSibling,
+                      getText(context, firstAdjacentSibling).replace(regex, replacement)
+                    );
+                  },
+                });
+
+                return;
+              }
+
+              if (isWithoutNewLine === prevent) return;
+
+              const messageId = prevent
+                ? 'prevent'
+                : 'require';
+
+              const regex = prevent
+                ? /(\n\n)(?!.*\1)/g
+                : /(\n)(?!.*\1)/g;
+
+              const replacement = prevent
+                ? '\n'
+                : '\n\n';
+
+              report(context, messages[messageId], messageId, {
+                node: secondAdjacentSibling,
+                fix(fixer) {
+                  return fixer.replaceText(
+                    firstAdjacentSibling,
+                    // double or remove the last newline
+                    getText(context, firstAdjacentSibling).replace(regex, replacement)
+                  );
+                },
+              });
+            }
+          });
+        });
+      },
+      ':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => {
+        jsxElementParents.add(node.parent);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-bind.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-bind.d.ts","sourceRoot":"","sources":["jsx-no-bind.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-bind.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+/**
+ * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
+ *               in React component props.
+ * @author Daniel Lo Nigro <dan.cx>
+ * @author Jacky Ho
+ */
+
+'use strict';
+
+const propName = require('jsx-ast-utils/propName');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+const getAncestors = require('../util/eslint').getAncestors;
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+const messages = {
+  bindCall: 'JSX props should not use .bind()',
+  arrowFunc: 'JSX props should not use arrow functions',
+  bindExpression: 'JSX props should not use ::',
+  func: 'JSX props should not use functions',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow `.bind()` or arrow functions in JSX props',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('jsx-no-bind'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowArrowFunctions: {
+          default: false,
+          type: 'boolean',
+        },
+        allowBind: {
+          default: false,
+          type: 'boolean',
+        },
+        allowFunctions: {
+          default: false,
+          type: 'boolean',
+        },
+        ignoreRefs: {
+          default: false,
+          type: 'boolean',
+        },
+        ignoreDOMComponents: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+
+    // Keep track of all the variable names pointing to a bind call,
+    // bind expression or an arrow function in different block statements
+    const blockVariableNameSets = {};
+
+    /**
+     * @param {string | number} blockStart
+     */
+    function setBlockVariableNameSet(blockStart) {
+      blockVariableNameSets[blockStart] = {
+        arrowFunc: new Set(),
+        bindCall: new Set(),
+        bindExpression: new Set(),
+        func: new Set(),
+      };
+    }
+
+    function getNodeViolationType(node) {
+      if (
+        !configuration.allowBind
+        && astUtil.isCallExpression(node)
+        && node.callee.type === 'MemberExpression'
+        && node.callee.property.type === 'Identifier'
+        && node.callee.property.name === 'bind'
+      ) {
+        return 'bindCall';
+      }
+      if (node.type === 'ConditionalExpression') {
+        return getNodeViolationType(node.test)
+               || getNodeViolationType(node.consequent)
+               || getNodeViolationType(node.alternate);
+      }
+      if (!configuration.allowArrowFunctions && node.type === 'ArrowFunctionExpression') {
+        return 'arrowFunc';
+      }
+      if (
+        !configuration.allowFunctions
+        && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration')
+      ) {
+        return 'func';
+      }
+      if (!configuration.allowBind && node.type === 'BindExpression') {
+        return 'bindExpression';
+      }
+
+      return null;
+    }
+
+    /**
+     * @param {string | number} violationType
+     * @param {unknown} variableName
+     * @param {string | number} blockStart
+     */
+    function addVariableNameToSet(violationType, variableName, blockStart) {
+      blockVariableNameSets[blockStart][violationType].add(variableName);
+    }
+
+    function getBlockStatementAncestors(node) {
+      return getAncestors(context, node).filter(
+        (ancestor) => ancestor.type === 'BlockStatement'
+      ).reverse();
+    }
+
+    function reportVariableViolation(node, name, blockStart) {
+      const blockSets = blockVariableNameSets[blockStart];
+      const violationTypes = Object.keys(blockSets);
+
+      return violationTypes.find((type) => {
+        if (blockSets[type].has(name)) {
+          report(context, messages[type], type, {
+            node,
+          });
+          return true;
+        }
+
+        return false;
+      });
+    }
+
+    function findVariableViolation(node, name) {
+      getBlockStatementAncestors(node).find(
+        (block) => reportVariableViolation(node, name, block.range[0])
+      );
+    }
+
+    return {
+      BlockStatement(node) {
+        setBlockVariableNameSet(node.range[0]);
+      },
+
+      FunctionDeclaration(node) {
+        const blockAncestors = getBlockStatementAncestors(node);
+        const variableViolationType = getNodeViolationType(node);
+
+        if (blockAncestors.length > 0 && variableViolationType) {
+          addVariableNameToSet(variableViolationType, node.id.name, blockAncestors[0].range[0]);
+        }
+      },
+
+      VariableDeclarator(node) {
+        if (!node.init) {
+          return;
+        }
+        const blockAncestors = getBlockStatementAncestors(node);
+        const variableViolationType = getNodeViolationType(node.init);
+
+        if (
+          blockAncestors.length > 0
+          && variableViolationType
+          && 'kind' in node.parent
+          && node.parent.kind === 'const' // only support const right now
+        ) {
+          addVariableNameToSet(variableViolationType, 'name' in node.id ? node.id.name : undefined, blockAncestors[0].range[0]);
+        }
+      },
+
+      JSXAttribute(node) {
+        const isRef = configuration.ignoreRefs && propName(node) === 'ref';
+        if (isRef || !node.value || !node.value.expression) {
+          return;
+        }
+        const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
+        if (configuration.ignoreDOMComponents && isDOMComponent) {
+          return;
+        }
+        const valueNode = node.value.expression;
+        const valueNodeType = valueNode.type;
+        const nodeViolationType = getNodeViolationType(valueNode);
+
+        if (valueNodeType === 'Identifier') {
+          findVariableViolation(node, valueNode.name);
+        } else if (nodeViolationType) {
+          report(context, messages[nodeViolationType], nodeViolationType, {
+            node,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-comment-textnodes.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-comment-textnodes.d.ts","sourceRoot":"","sources":["jsx-no-comment-textnodes.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Comments inside children section of tag should be placed inside braces.
+ * @author Ben Vinegar
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  putCommentInBraces: 'Comments inside children section of tag should be placed inside braces',
+};
+
+/**
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {void}
+ */
+function checkText(context, node) {
+  // since babel-eslint has the wrong node.raw, we'll get the source text
+  const rawValue = getText(context, node);
+  if (/^\s*\/(\/|\*)/m.test(rawValue)) {
+    // inside component, e.g. <div>literal</div>
+    if (
+      node.parent.type !== 'JSXAttribute'
+      && node.parent.type !== 'JSXExpressionContainer'
+      && node.parent.type.indexOf('JSX') !== -1
+    ) {
+      report(context, messages.putCommentInBraces, 'putCommentInBraces', {
+        node,
+      });
+    }
+  }
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow comments from being inserted as text nodes',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('jsx-no-comment-textnodes'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      Literal(node) {
+        checkText(context, node);
+      },
+      JSXText(node) {
+        checkText(context, node);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-constructed-context-values.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-constructed-context-values.d.ts","sourceRoot":"","sources":["jsx-no-constructed-context-values.js"],"names":[],"mappings":"wBA2KW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,272 @@
+/**
+ * @fileoverview Prevents jsx context provider values from taking values that
+ *               will cause needless rerenders.
+ * @author Dylan Oshima
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const getScope = require('../util/eslint').getScope;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+// Recursively checks if an element is a construction.
+// A construction is a variable that changes identity every render.
+function isConstruction(node, callScope) {
+  switch (node.type) {
+    case 'Literal':
+      if (node.regex != null) {
+        return { type: 'regular expression', node };
+      }
+      return null;
+    case 'Identifier': {
+      const variableScoping = callScope.set.get(node.name);
+
+      if (variableScoping == null || variableScoping.defs == null) {
+        // If it's not in scope, we don't care.
+        return null; // Handled
+      }
+
+      // Gets the last variable identity
+      const variableDefs = variableScoping.defs;
+      const def = variableDefs[variableDefs.length - 1];
+      if (def != null
+        && def.type !== 'Variable'
+        && def.type !== 'FunctionName'
+      ) {
+        // Parameter or an unusual pattern. Bail out.
+        return null; // Unhandled
+      }
+
+      if (def.node.type === 'FunctionDeclaration') {
+        return { type: 'function declaration', node: def.node, usage: node };
+      }
+
+      const init = def.node.init;
+      if (init == null) {
+        return null;
+      }
+
+      const initConstruction = isConstruction(init, callScope);
+      if (initConstruction == null) {
+        return null;
+      }
+
+      return {
+        type: initConstruction.type,
+        node: initConstruction.node,
+        usage: node,
+      };
+    }
+    case 'ObjectExpression':
+      // Any object initialized inline will create a new identity
+      return { type: 'object', node };
+    case 'ArrayExpression':
+      return { type: 'array', node };
+    case 'ArrowFunctionExpression':
+    case 'FunctionExpression':
+      // Functions that are initialized inline will have a new identity
+      return { type: 'function expression', node };
+    case 'ClassExpression':
+      return { type: 'class expression', node };
+    case 'NewExpression':
+      // `const a = new SomeClass();` is a construction
+      return { type: 'new expression', node };
+    case 'ConditionalExpression':
+      return (isConstruction(node.consequent, callScope)
+        || isConstruction(node.alternate, callScope)
+      );
+    case 'LogicalExpression':
+      return (isConstruction(node.left, callScope)
+        || isConstruction(node.right, callScope)
+      );
+    case 'MemberExpression': {
+      const objConstruction = isConstruction(node.object, callScope);
+      if (objConstruction == null) {
+        return null;
+      }
+      return {
+        type: objConstruction.type,
+        node: objConstruction.node,
+        usage: node.object,
+      };
+    }
+    case 'JSXFragment':
+      return { type: 'JSX fragment', node };
+    case 'JSXElement':
+      return { type: 'JSX element', node };
+    case 'AssignmentExpression': {
+      const construct = isConstruction(node.right, callScope);
+      if (construct != null) {
+        return {
+          type: 'assignment expression',
+          node: construct.node,
+          usage: node,
+        };
+      }
+      return null;
+    }
+    case 'TypeCastExpression':
+    case 'TSAsExpression':
+      return isConstruction(node.expression, callScope);
+    default:
+      return null;
+  }
+}
+
+function isReactContext(context, node) {
+  let scope = getScope(context, node);
+  let variableScoping = null;
+  const contextName = node.name;
+
+  while (scope && !variableScoping) { // Walk up the scope chain to find the variable
+    variableScoping = scope.set.get(contextName);
+    scope = scope.upper;
+  }
+
+  if (!variableScoping) { // Context was not found in scope
+    return false;
+  }
+
+  // Get the variable's definition
+  const def = variableScoping.defs[0];
+
+  if (!def || def.node.type !== 'VariableDeclarator') {
+    return false;
+  }
+
+  const init = def.node.init; // Variable initializer
+
+  const isCreateContext = init
+    && init.type === 'CallExpression'
+    && (
+      (
+        init.callee.type === 'Identifier'
+        && init.callee.name === 'createContext'
+      ) || (
+        init.callee.type === 'MemberExpression'
+        && init.callee.object.name === 'React'
+        && init.callee.property.name === 'createContext'
+      )
+    );
+
+  return isCreateContext;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  withIdentifierMsg: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useMemo hook.",
+  withIdentifierMsgFunc: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.",
+  defaultMsg: 'The {{type}} passed as the value prop to the Context provider (at line {{nodeLine}}) changes every render. To fix this consider wrapping it in a useMemo hook.',
+  defaultMsgFunc: 'The {{type}} passed as the value prop to the Context provider (at line {{nodeLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallows JSX context provider values from taking values that will cause needless rerenders',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('jsx-no-constructed-context-values'),
+    },
+    messages,
+    schema: false,
+  },
+
+  // eslint-disable-next-line arrow-body-style
+  create: Components.detect((context, components, utils) => {
+    return {
+      JSXOpeningElement(node) {
+        const openingElementName = node.name;
+
+        if (openingElementName.type === 'JSXMemberExpression') {
+          const isJSXContext = openingElementName.property.name === 'Provider';
+          if (!isJSXContext) {
+            // Member is not Provider
+            return;
+          }
+        } else if (openingElementName.type === 'JSXIdentifier') {
+          const isJSXContext = isReactContext(context, openingElementName);
+          if (!isJSXContext) {
+            // Member is not context
+            return;
+          }
+        } else {
+          return;
+        }
+
+        // Contexts can take in more than just a value prop
+        // so we need to iterate through all of them
+        const jsxValueAttribute = node.attributes.find(
+          (attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === 'value'
+        );
+
+        if (jsxValueAttribute == null) {
+          // No value prop was passed
+          return;
+        }
+
+        const valueNode = jsxValueAttribute.value;
+        if (!valueNode) {
+          // attribute is a boolean shorthand
+          return;
+        }
+        if (valueNode.type !== 'JSXExpressionContainer') {
+          // value could be a literal
+          return;
+        }
+
+        const valueExpression = valueNode.expression;
+        const invocationScope = getScope(context, node);
+
+        // Check if the value prop is a construction
+        const constructInfo = isConstruction(valueExpression, invocationScope);
+        if (constructInfo == null) {
+          return;
+        }
+
+        if (!utils.getParentComponent(node)) {
+          return;
+        }
+
+        // Report found error
+        const constructType = constructInfo.type;
+        const constructNode = constructInfo.node;
+        const constructUsage = constructInfo.usage;
+        const data = {
+          type: constructType, nodeLine: constructNode.loc.start.line,
+        };
+        let messageId = 'defaultMsg';
+
+        // Variable passed to value prop
+        if (constructUsage != null) {
+          messageId = 'withIdentifierMsg';
+          data.usageLine = constructUsage.loc.start.line;
+          data.variableName = constructUsage.name;
+        }
+
+        // Type of expression
+        if (
+          constructType === 'function expression'
+          || constructType === 'function declaration'
+        ) {
+          messageId += 'Func';
+        }
+
+        report(context, messages[messageId], messageId, {
+          node: constructNode,
+          data,
+        });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-duplicate-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-duplicate-props.d.ts","sourceRoot":"","sources":["jsx-no-duplicate-props.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,77 @@
+/**
+ * @fileoverview Enforce no duplicate props
+ * @author Markus Ånöstam
+ */
+
+'use strict';
+
+const has = require('hasown');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noDuplicateProps: 'No duplicate props allowed',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow duplicate properties in JSX',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('jsx-no-duplicate-props'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreCase: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const ignoreCase = configuration.ignoreCase || false;
+
+    return {
+      JSXOpeningElement(node) {
+        const props = {};
+
+        node.attributes.forEach((decl) => {
+          if (decl.type === 'JSXSpreadAttribute') {
+            return;
+          }
+
+          let name = decl.name.name;
+
+          if (typeof name !== 'string') {
+            return;
+          }
+
+          if (ignoreCase) {
+            name = name.toLowerCase();
+          }
+
+          if (has(props, name)) {
+            report(context, messages.noDuplicateProps, 'noDuplicateProps', {
+              node: decl,
+            });
+          } else {
+            props[name] = 1;
+          }
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-leaked-render.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-leaked-render.d.ts","sourceRoot":"","sources":["jsx-no-leaked-render.js"],"names":[],"mappings":"wBA+GW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,204 @@
+/**
+ * @fileoverview Prevent problematic leaked values from being rendered
+ * @author Mario Beltrán
+ */
+
+'use strict';
+
+const find = require('es-iterator-helpers/Iterator.prototype.find');
+const from = require('es-iterator-helpers/Iterator.from');
+
+const getText = require('../util/eslint').getText;
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const variableUtil = require('../util/variable');
+const testReactVersion = require('../util/version').testReactVersion;
+const isParenthesized = require('../util/ast').isParenthesized;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const messages = {
+  noPotentialLeakedRender: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
+};
+
+const COERCE_STRATEGY = 'coerce';
+const TERNARY_STRATEGY = 'ternary';
+const DEFAULT_VALID_STRATEGIES = [TERNARY_STRATEGY, COERCE_STRATEGY];
+const COERCE_VALID_LEFT_SIDE_EXPRESSIONS = ['UnaryExpression', 'BinaryExpression', 'CallExpression'];
+const TERNARY_INVALID_ALTERNATE_VALUES = [undefined, null, false];
+
+function trimLeftNode(node) {
+  // Remove double unary expression (boolean coercion), so we avoid trimming valid negations
+  if (node.type === 'UnaryExpression' && node.argument.type === 'UnaryExpression') {
+    return trimLeftNode(node.argument.argument);
+  }
+
+  return node;
+}
+
+function getIsCoerceValidNestedLogicalExpression(node) {
+  if (node.type === 'LogicalExpression') {
+    return getIsCoerceValidNestedLogicalExpression(node.left) && getIsCoerceValidNestedLogicalExpression(node.right);
+  }
+
+  return COERCE_VALID_LEFT_SIDE_EXPRESSIONS.some((validExpression) => validExpression === node.type);
+}
+
+function extractExpressionBetweenLogicalAnds(node) {
+  if (node.type !== 'LogicalExpression') return [node];
+  if (node.operator !== '&&') return [node];
+  return [].concat(
+    extractExpressionBetweenLogicalAnds(node.left),
+    extractExpressionBetweenLogicalAnds(node.right)
+  );
+}
+
+function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) {
+  const rightSideText = getText(context, rightNode);
+
+  if (fixStrategy === COERCE_STRATEGY) {
+    const expressions = extractExpressionBetweenLogicalAnds(leftNode);
+    const newText = expressions.map((node) => {
+      let nodeText = getText(context, node);
+      if (isParenthesized(context, node)) {
+        nodeText = `(${nodeText})`;
+      }
+      if (node.parent && node.parent.type === 'ConditionalExpression' && node.parent.consequent.value === false) {
+        return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!'}${nodeText}`;
+      }
+      return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`;
+    }).join(' && ');
+
+    if (rightNode.parent && rightNode.parent.type === 'ConditionalExpression' && rightNode.parent.consequent.value === false) {
+      const consequentVal = rightNode.parent.consequent.raw || rightNode.parent.consequent.name;
+      const alternateVal = rightNode.parent.alternate.raw || rightNode.parent.alternate.name;
+      if (rightNode.parent.test && rightNode.parent.test.type === 'LogicalExpression') {
+        return fixer.replaceText(reportedNode, `${newText} ? ${consequentVal} : ${alternateVal}`);
+      }
+      return fixer.replaceText(reportedNode, `${newText} && ${alternateVal}`);
+    }
+
+    if (rightNode.type === 'ConditionalExpression' || rightNode.type === 'LogicalExpression') {
+      return fixer.replaceText(reportedNode, `${newText} && (${rightSideText})`);
+    }
+    if (rightNode.type === 'JSXElement') {
+      const rightSideTextLines = rightSideText.split('\n');
+      if (rightSideTextLines.length > 1) {
+        const rightSideTextLastLine = rightSideTextLines[rightSideTextLines.length - 1];
+        const indentSpacesStart = ' '.repeat(rightSideTextLastLine.search(/\S/));
+        const indentSpacesClose = ' '.repeat(rightSideTextLastLine.search(/\S/) - 2);
+        return fixer.replaceText(reportedNode, `${newText} && (\n${indentSpacesStart}${rightSideText}\n${indentSpacesClose})`);
+      }
+    }
+    if (rightNode.type === 'Literal') {
+      return null;
+    }
+    return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`);
+  }
+
+  if (fixStrategy === TERNARY_STRATEGY) {
+    let leftSideText = getText(context, trimLeftNode(leftNode));
+    if (isParenthesized(context, leftNode)) {
+      leftSideText = `(${leftSideText})`;
+    }
+    return fixer.replaceText(reportedNode, `${leftSideText} ? ${rightSideText} : null`);
+  }
+
+  throw new TypeError('Invalid value for "validStrategies" option');
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow problematic leaked values from being rendered',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('jsx-no-leaked-render'),
+    },
+
+    messages,
+
+    fixable: 'code',
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          validStrategies: {
+            type: 'array',
+            items: {
+              enum: [
+                TERNARY_STRATEGY,
+                COERCE_STRATEGY,
+              ],
+            },
+            uniqueItems: true,
+            default: DEFAULT_VALID_STRATEGIES,
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
+  },
+
+  create(context) {
+    const config = context.options[0] || {};
+    const validStrategies = new Set(config.validStrategies || DEFAULT_VALID_STRATEGIES);
+    const fixStrategy = find(from(validStrategies), () => true);
+
+    return {
+      'JSXExpressionContainer > LogicalExpression[operator="&&"]'(node) {
+        const leftSide = node.left;
+
+        const isCoerceValidLeftSide = COERCE_VALID_LEFT_SIDE_EXPRESSIONS
+          .some((validExpression) => validExpression === leftSide.type);
+        if (validStrategies.has(COERCE_STRATEGY)) {
+          if (isCoerceValidLeftSide || getIsCoerceValidNestedLogicalExpression(leftSide)) {
+            return;
+          }
+          const leftSideVar = variableUtil.getVariableFromContext(context, node, leftSide.name);
+          if (leftSideVar) {
+            const leftSideValue = leftSideVar.defs
+              && leftSideVar.defs.length
+              && leftSideVar.defs[0].node.init
+              && leftSideVar.defs[0].node.init.value;
+            if (typeof leftSideValue === 'boolean') {
+              return;
+            }
+          }
+        }
+
+        if (testReactVersion(context, '>= 18') && leftSide.type === 'Literal' && leftSide.value === '') {
+          return;
+        }
+        report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', {
+          node,
+          fix(fixer) {
+            return ruleFixer(context, fixStrategy, fixer, node, leftSide, node.right);
+          },
+        });
+      },
+
+      'JSXExpressionContainer > ConditionalExpression'(node) {
+        if (validStrategies.has(TERNARY_STRATEGY)) {
+          return;
+        }
+
+        const isValidTernaryAlternate = TERNARY_INVALID_ALTERNATE_VALUES.indexOf(node.alternate.value) === -1;
+        const isJSXElementAlternate = node.alternate.type === 'JSXElement';
+        if (isValidTernaryAlternate || isJSXElementAlternate) {
+          return;
+        }
+
+        report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', {
+          node,
+          fix(fixer) {
+            return ruleFixer(context, fixStrategy, fixer, node, node.test, node.consequent);
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+declare const _exports: RuleModule;
+export = _exports;
+export type RuleModule = import("eslint").Rule.RuleModule;
+export type Config = {
+    type: "element";
+} & import("../../types/rules/jsx-no-literals").ElementConfigProperties & import("../../types/rules/jsx-no-literals").ElementOverrides;
+export type RawConfig = import("../../types/rules/jsx-no-literals").RawElementConfig & import("../../types/rules/jsx-no-literals").RawElementOverrides;
+export type ResolvedConfig = import("../../types/rules/jsx-no-literals").OverrideConfig | import("../../types/rules/jsx-no-literals").Config;
+export type OverrideConfig = import("../../types/rules/jsx-no-literals").OverrideConfigProperties & import("../../types/rules/jsx-no-literals").ElementConfigProperties;
+export type ElementConfig = {
+    type: "element";
+} & import("../../types/rules/jsx-no-literals").ElementConfigProperties;
+//# sourceMappingURL=jsx-no-literals.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-literals.d.ts","sourceRoot":"","sources":["jsx-no-literals.js"],"names":[],"mappings":"wBAoJW,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-literals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,524 @@
+/**
+ * @fileoverview Prevent using string literals in React component definition
+ * @author Caleb Morris
+ * @author David Buchan-Swanson
+ */
+
+'use strict';
+
+const iterFrom = require('es-iterator-helpers/Iterator.from');
+const map = require('es-iterator-helpers/Iterator.prototype.map');
+const some = require('es-iterator-helpers/Iterator.prototype.some');
+const flatMap = require('es-iterator-helpers/Iterator.prototype.flatMap');
+const fromEntries = require('object.fromentries');
+const entries = require('object.entries');
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+/** @typedef {import('eslint').Rule.RuleModule} RuleModule */
+
+/** @typedef {import('../../types/rules/jsx-no-literals').Config} Config */
+/** @typedef {import('../../types/rules/jsx-no-literals').RawConfig} RawConfig */
+/** @typedef {import('../../types/rules/jsx-no-literals').ResolvedConfig} ResolvedConfig */
+/** @typedef {import('../../types/rules/jsx-no-literals').OverrideConfig} OverrideConfig */
+/** @typedef {import('../../types/rules/jsx-no-literals').ElementConfig} ElementConfig */
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+/**
+ * @param {unknown} value
+ * @returns {string | unknown}
+ */
+function trimIfString(value) {
+  return typeof value === 'string' ? value.trim() : value;
+}
+
+const reOverridableElement = /^[A-Z][\w.]*$/;
+const reIsWhiteSpace = /^[\s]+$/;
+const jsxElementTypes = new Set(['JSXElement', 'JSXFragment']);
+const standardJSXNodeParentTypes = new Set(['JSXAttribute', 'JSXElement', 'JSXExpressionContainer', 'JSXFragment']);
+
+const messages = {
+  invalidPropValue: 'Invalid prop value: "{{text}}"',
+  invalidPropValueInElement: 'Invalid prop value: "{{text}}" in {{element}}',
+  noStringsInAttributes: 'Strings not allowed in attributes: "{{text}}"',
+  noStringsInAttributesInElement: 'Strings not allowed in attributes: "{{text}}" in {{element}}',
+  noStringsInJSX: 'Strings not allowed in JSX files: "{{text}}"',
+  noStringsInJSXInElement: 'Strings not allowed in JSX files: "{{text}}" in {{element}}',
+  literalNotInJSXExpression: 'Missing JSX expression container around literal string: "{{text}}"',
+  literalNotInJSXExpressionInElement: 'Missing JSX expression container around literal string: "{{text}}" in {{element}}',
+};
+
+/** @type {Exclude<RuleModule['meta']['schema'], unknown[] | false>['properties']} */
+const commonPropertiesSchema = {
+  noStrings: {
+    type: 'boolean',
+  },
+  allowedStrings: {
+    type: 'array',
+    uniqueItems: true,
+    items: {
+      type: 'string',
+    },
+  },
+  ignoreProps: {
+    type: 'boolean',
+  },
+  noAttributeStrings: {
+    type: 'boolean',
+  },
+};
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * Normalizes the element portion of the config
+ * @param {RawConfig} config
+ * @returns {ElementConfig}
+ */
+function normalizeElementConfig(config) {
+  return {
+    type: 'element',
+    noStrings: !!config.noStrings,
+    allowedStrings: config.allowedStrings
+      ? new Set(map(iterFrom(config.allowedStrings), trimIfString))
+      : new Set(),
+    ignoreProps: !!config.ignoreProps,
+    noAttributeStrings: !!config.noAttributeStrings,
+  };
+}
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * Normalizes the config and applies default values to all config options
+ * @param {RawConfig} config
+ * @returns {Config}
+ */
+function normalizeConfig(config) {
+  /** @type {Config} */
+  const normalizedConfig = Object.assign(normalizeElementConfig(config), {
+    elementOverrides: {},
+  });
+
+  if (config.elementOverrides) {
+    normalizedConfig.elementOverrides = fromEntries(
+      flatMap(
+        iterFrom(entries(config.elementOverrides)),
+        (entry) => {
+          const elementName = entry[0];
+          const rawElementConfig = entry[1];
+
+          if (!reOverridableElement.test(elementName)) {
+            return [];
+          }
+
+          return [[
+            elementName,
+            Object.assign(normalizeElementConfig(rawElementConfig), {
+              type: 'override',
+              name: elementName,
+              allowElement: !!rawElementConfig.allowElement,
+              applyToNestedElements: typeof rawElementConfig.applyToNestedElements === 'undefined' || !!rawElementConfig.applyToNestedElements,
+            }),
+          ]];
+        }
+      )
+    );
+  }
+
+  return normalizedConfig;
+}
+
+const elementOverrides = {
+  type: 'object',
+  patternProperties: {
+    [reOverridableElement.source]: {
+      type: 'object',
+      properties: Object.assign(
+        { applyToNestedElements: { type: 'boolean' } },
+        commonPropertiesSchema
+      ),
+
+    },
+  },
+};
+
+/** @type {RuleModule} */
+module.exports = {
+  meta: /** @type {RuleModule['meta']} */ ({
+    docs: {
+      description: 'Disallow usage of string literals in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-no-literals'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: Object.assign(
+        { elementOverrides },
+        commonPropertiesSchema
+      ),
+      additionalProperties: false,
+    }],
+  }),
+
+  create(context) {
+    /** @type {RawConfig} */
+    const rawConfig = (context.options.length && context.options[0]) || {};
+    const config = normalizeConfig(rawConfig);
+
+    const hasElementOverrides = Object.keys(config.elementOverrides).length > 0;
+
+    /** @type {Map<string, string>} */
+    const renamedImportMap = new Map();
+
+    /**
+     * Determines if the given expression is a require statement. Supports
+     * nested MemberExpresions. ie `require('foo').nested.property`
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    function isRequireStatement(node) {
+      if (node.type === 'CallExpression') {
+        if (node.callee.type === 'Identifier') {
+          return node.callee.name === 'require';
+        }
+      }
+      if (node.type === 'MemberExpression') {
+        return isRequireStatement(node.object);
+      }
+
+      return false;
+    }
+
+    /** @typedef {{ name: string, compoundName?: string }} ElementNameFragment */
+
+    /**
+     * Gets the name of the given JSX element. Supports nested
+     * JSXMemeberExpressions. ie `<Namesapce.Component.SubComponent />`
+     * @param {ASTNode} node
+     * @returns {ElementNameFragment | undefined}
+     */
+    function getJSXElementName(node) {
+      if (node.openingElement.name.type === 'JSXIdentifier') {
+        const name = node.openingElement.name.name;
+        return {
+          name: renamedImportMap.get(name) || name,
+          compoundName: undefined,
+        };
+      }
+
+      /** @type {string[]} */
+      const nameFragments = [];
+
+      if (node.openingElement.name.type === 'JSXMemberExpression') {
+        /** @type {ASTNode} */
+        let current = node.openingElement.name;
+        while (current.type === 'JSXMemberExpression') {
+          if (current.property.type === 'JSXIdentifier') {
+            nameFragments.unshift(current.property.name);
+          }
+
+          current = current.object;
+        }
+
+        if (current.type === 'JSXIdentifier') {
+          nameFragments.unshift(current.name);
+
+          const rootFragment = nameFragments[0];
+          if (rootFragment) {
+            const rootFragmentRenamed = renamedImportMap.get(rootFragment);
+            if (rootFragmentRenamed) {
+              nameFragments[0] = rootFragmentRenamed;
+            }
+          }
+
+          const nameFragment = nameFragments[nameFragments.length - 1];
+          if (nameFragment) {
+            return {
+              name: nameFragment,
+              compoundName: nameFragments.join('.'),
+            };
+          }
+        }
+      }
+    }
+
+    /**
+     * Gets all JSXElement ancestor nodes for the given node
+     * @param {ASTNode} node
+     * @returns {ASTNode[]}
+     */
+    function getJSXElementAncestors(node) {
+      /** @type {ASTNode[]} */
+      const ancestors = [];
+
+      let current = node;
+      while (current) {
+        if (current.type === 'JSXElement') {
+          ancestors.push(current);
+        }
+
+        current = current.parent;
+      }
+
+      return ancestors;
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {ASTNode}
+     */
+    function getParentIgnoringBinaryExpressions(node) {
+      let current = node;
+      while (current.parent.type === 'BinaryExpression') {
+        current = current.parent;
+      }
+      return current.parent;
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {{ parent: ASTNode, grandParent: ASTNode }}
+     */
+    function getParentAndGrandParent(node) {
+      const parent = getParentIgnoringBinaryExpressions(node);
+      return {
+        parent,
+        grandParent: parent.parent,
+      };
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    function hasJSXElementParentOrGrandParent(node) {
+      const ancestors = getParentAndGrandParent(node);
+      return some(iterFrom([ancestors.parent, ancestors.grandParent]), (parent) => jsxElementTypes.has(parent.type));
+    }
+
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * Determines whether a given node's value and its immediate parent are
+     * viable text nodes that can/should be reported on
+     * @param {ASTNode} node
+     * @param {ResolvedConfig} resolvedConfig
+     * @returns {boolean}
+     */
+    function isViableTextNode(node, resolvedConfig) {
+      const textValues = iterFrom([trimIfString(node.raw), trimIfString(node.value)]);
+      if (some(textValues, (value) => resolvedConfig.allowedStrings.has(value))) {
+        return false;
+      }
+
+      const parent = getParentIgnoringBinaryExpressions(node);
+
+      let isStandardJSXNode = false;
+      if (typeof node.value === 'string' && !reIsWhiteSpace.test(node.value) && standardJSXNodeParentTypes.has(parent.type)) {
+        if (resolvedConfig.noAttributeStrings) {
+          isStandardJSXNode = parent.type === 'JSXAttribute' || parent.type === 'JSXElement';
+        } else {
+          isStandardJSXNode = parent.type !== 'JSXAttribute';
+        }
+      }
+
+      if (resolvedConfig.noStrings) {
+        return isStandardJSXNode;
+      }
+
+      return isStandardJSXNode && parent.type !== 'JSXExpressionContainer';
+    }
+
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * Gets an override config for a given node. For any given node, we also
+     * need to traverse the ancestor tree to determine if an ancestor's config
+     * will also apply to the current node.
+     * @param {ASTNode} node
+     * @returns {OverrideConfig | undefined}
+     */
+    function getOverrideConfig(node) {
+      if (!hasElementOverrides) {
+        return;
+      }
+
+      const allAncestorElements = getJSXElementAncestors(node);
+      if (!allAncestorElements.length) {
+        return;
+      }
+
+      for (const ancestorElement of allAncestorElements) {
+        const isClosestJSXAncestor = ancestorElement === allAncestorElements[0];
+
+        const ancestor = getJSXElementName(ancestorElement);
+        if (ancestor) {
+          if (ancestor.name) {
+            const ancestorElements = config.elementOverrides[ancestor.name];
+            const ancestorConfig = ancestor.compoundName
+              ? config.elementOverrides[ancestor.compoundName] || ancestorElements
+              : ancestorElements;
+
+            if (ancestorConfig) {
+              if (isClosestJSXAncestor || ancestorConfig.applyToNestedElements) {
+                return ancestorConfig;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * @param {ResolvedConfig} resolvedConfig
+     * @returns {boolean}
+     */
+    function shouldAllowElement(resolvedConfig) {
+      return resolvedConfig.type === 'override' && 'allowElement' in resolvedConfig && !!resolvedConfig.allowElement;
+    }
+
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * @param {boolean} ancestorIsJSXElement
+     * @param {ResolvedConfig} resolvedConfig
+     * @returns {string}
+     */
+    function defaultMessageId(ancestorIsJSXElement, resolvedConfig) {
+      if (resolvedConfig.noAttributeStrings && !ancestorIsJSXElement) {
+        return resolvedConfig.type === 'override' ? 'noStringsInAttributesInElement' : 'noStringsInAttributes';
+      }
+
+      if (resolvedConfig.noStrings) {
+        return resolvedConfig.type === 'override' ? 'noStringsInJSXInElement' : 'noStringsInJSX';
+      }
+
+      return resolvedConfig.type === 'override' ? 'literalNotInJSXExpressionInElement' : 'literalNotInJSXExpression';
+    }
+
+    // eslint-disable-next-line valid-jsdoc
+    /**
+     * @param {ASTNode} node
+     * @param {string} messageId
+     * @param {ResolvedConfig} resolvedConfig
+     */
+    function reportLiteralNode(node, messageId, resolvedConfig) {
+      report(context, messages[messageId], messageId, {
+        node,
+        data: {
+          text: getText(context, node).trim(),
+          element: resolvedConfig.type === 'override' && 'name' in resolvedConfig ? resolvedConfig.name : undefined,
+        },
+      });
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return Object.assign(hasElementOverrides ? {
+      // Get renamed import local names mapped to their imported name
+      ImportDeclaration(node) {
+        node.specifiers
+          .filter((s) => s.type === 'ImportSpecifier')
+          .forEach((specifier) => {
+            renamedImportMap.set(
+              (specifier.local || specifier.imported).name,
+              specifier.imported.name
+            );
+          });
+      },
+
+      // Get renamed destructured local names mapped to their imported name
+      VariableDeclaration(node) {
+        node.declarations
+          .filter((d) => (
+            d.type === 'VariableDeclarator'
+            && isRequireStatement(d.init)
+            && d.id.type === 'ObjectPattern'
+          ))
+          .forEach((declaration) => {
+            declaration.id.properties
+              .filter((property) => (
+                property.type === 'Property'
+                && property.key.type === 'Identifier'
+                && property.value.type === 'Identifier'
+              ))
+              .forEach((property) => {
+                renamedImportMap.set(property.value.name, property.key.name);
+              });
+          });
+      },
+    } : false, {
+      Literal(node) {
+        const resolvedConfig = getOverrideConfig(node) || config;
+
+        const hasJSXParentOrGrandParent = hasJSXElementParentOrGrandParent(node);
+        if (hasJSXParentOrGrandParent && shouldAllowElement(resolvedConfig)) {
+          return;
+        }
+
+        if (isViableTextNode(node, resolvedConfig)) {
+          if (hasJSXParentOrGrandParent || !config.ignoreProps) {
+            reportLiteralNode(node, defaultMessageId(hasJSXParentOrGrandParent, resolvedConfig), resolvedConfig);
+          }
+        }
+      },
+
+      JSXAttribute(node) {
+        const isLiteralString = node.value && node.value.type === 'Literal'
+          && typeof node.value.value === 'string';
+        const isStringLiteral = node.value && node.value.type === 'StringLiteral';
+
+        if (isLiteralString || isStringLiteral) {
+          const resolvedConfig = getOverrideConfig(node) || config;
+
+          if (
+            resolvedConfig.noStrings
+            && !resolvedConfig.ignoreProps
+            && !resolvedConfig.allowedStrings.has(node.value.value)
+          ) {
+            const messageId = resolvedConfig.type === 'override' ? 'invalidPropValueInElement' : 'invalidPropValue';
+            reportLiteralNode(node, messageId, resolvedConfig);
+          }
+        }
+      },
+
+      JSXText(node) {
+        const resolvedConfig = getOverrideConfig(node) || config;
+
+        if (shouldAllowElement(resolvedConfig)) {
+          return;
+        }
+
+        if (isViableTextNode(node, resolvedConfig)) {
+          const hasJSXParendOrGrantParent = hasJSXElementParentOrGrandParent(node);
+          reportLiteralNode(node, defaultMessageId(hasJSXParendOrGrantParent, resolvedConfig), resolvedConfig);
+        }
+      },
+
+      TemplateLiteral(node) {
+        const ancestors = getParentAndGrandParent(node);
+        const isParentJSXExpressionCont = ancestors.parent.type === 'JSXExpressionContainer';
+        const isParentJSXElement = ancestors.grandParent.type === 'JSXElement';
+
+        if (isParentJSXExpressionCont) {
+          const resolvedConfig = getOverrideConfig(node) || config;
+
+          if (
+            resolvedConfig.noStrings
+            && (isParentJSXElement || !resolvedConfig.ignoreProps)
+          ) {
+            reportLiteralNode(node, defaultMessageId(isParentJSXElement, resolvedConfig), resolvedConfig);
+          }
+        }
+      },
+    });
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-script-url.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-script-url.d.ts","sourceRoot":"","sources":["jsx-no-script-url.js"],"names":[],"mappings":"wBA6CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-script-url.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,143 @@
+/**
+ * @fileoverview Prevent usage of `javascript:` URLs
+ * @author Sergei Startsev
+ */
+
+'use strict';
+
+const includes = require('array-includes');
+const docsUrl = require('../util/docsUrl');
+const linkComponentsUtil = require('../util/linkComponents');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+// https://github.com/facebook/react/blob/d0ebde77f6d1232cefc0da184d731943d78e86f2/packages/react-dom/src/shared/sanitizeURL.js#L30
+/* eslint-disable-next-line max-len, no-control-regex */
+const isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
+
+function hasJavaScriptProtocol(attr) {
+  return attr.value && attr.value.type === 'Literal'
+    && isJavaScriptProtocol.test(attr.value.value);
+}
+
+function shouldVerifyProp(node, config) {
+  const name = node.name && node.name.name;
+  const parentName = node.parent.name && node.parent.name.name;
+
+  if (!name || !parentName || !config.has(parentName)) return false;
+
+  const attributes = config.get(parentName);
+  return includes(attributes, name);
+}
+
+function parseLegacyOption(config, option) {
+  option.forEach((opt) => {
+    config.set(opt.name, opt.props);
+  });
+}
+
+const messages = {
+  noScriptURL: 'A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of `javascript:` URLs',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('jsx-no-script-url'),
+    },
+
+    messages,
+
+    schema: {
+      anyOf: [
+        {
+          type: 'array',
+          items: [
+            {
+              type: 'array',
+              uniqueItems: true,
+              items: {
+                type: 'object',
+                properties: {
+                  name: {
+                    type: 'string',
+                  },
+                  props: {
+                    type: 'array',
+                    items: {
+                      type: 'string',
+                      uniqueItems: true,
+                    },
+                  },
+                },
+                required: ['name', 'props'],
+                additionalProperties: false,
+              },
+            },
+            {
+              type: 'object',
+              properties: {
+                includeFromSettings: {
+                  type: 'boolean',
+                },
+              },
+              additionalItems: false,
+            },
+          ],
+          additionalItems: false,
+        },
+        {
+          type: 'array',
+          items: [
+            {
+              type: 'object',
+              properties: {
+                includeFromSettings: {
+                  type: 'boolean',
+                },
+              },
+              additionalItems: false,
+            },
+          ],
+          additionalItems: false,
+        },
+      ],
+    },
+  },
+
+  create(context) {
+    const options = context.options;
+    const hasLegacyOption = Array.isArray(options[0]);
+    const legacyOptions = hasLegacyOption ? options[0] : [];
+    // eslint-disable-next-line no-nested-ternary
+    const objectOption = (hasLegacyOption && options.length > 1)
+      ? options[1]
+      : (options.length > 0
+        ? options[0]
+        : {
+          includeFromSettings: false,
+        }
+      );
+    const includeFromSettings = objectOption.includeFromSettings;
+
+    const linkComponents = linkComponentsUtil.getLinkComponents(includeFromSettings ? context : {});
+    parseLegacyOption(linkComponents, legacyOptions);
+
+    return {
+      JSXAttribute(node) {
+        if (shouldVerifyProp(node, linkComponents) && hasJavaScriptProtocol(node)) {
+          report(context, messages.noScriptURL, 'noScriptURL', {
+            node,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-target-blank.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-target-blank.d.ts","sourceRoot":"","sources":["jsx-no-target-blank.js"],"names":[],"mappings":"wBAgIW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-target-blank.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,285 @@
+/**
+ * @fileoverview Forbid target='_blank' attribute
+ * @author Kevin Miller
+ */
+
+'use strict';
+
+const includes = require('array-includes');
+const docsUrl = require('../util/docsUrl');
+const linkComponentsUtil = require('../util/linkComponents');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function findLastIndex(arr, condition) {
+  for (let i = arr.length - 1; i >= 0; i -= 1) {
+    if (condition(arr[i])) {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+function attributeValuePossiblyBlank(attribute) {
+  if (!attribute || !attribute.value) {
+    return false;
+  }
+  const value = attribute.value;
+  if (value.type === 'Literal') {
+    return typeof value.value === 'string' && value.value.toLowerCase() === '_blank';
+  }
+  if (value.type === 'JSXExpressionContainer') {
+    const expr = value.expression;
+    if (expr.type === 'Literal') {
+      return typeof expr.value === 'string' && expr.value.toLowerCase() === '_blank';
+    }
+    if (expr.type === 'ConditionalExpression') {
+      if (expr.alternate.type === 'Literal' && expr.alternate.value && expr.alternate.value.toLowerCase() === '_blank') {
+        return true;
+      }
+      if (expr.consequent.type === 'Literal' && expr.consequent.value && expr.consequent.value.toLowerCase() === '_blank') {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+function hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex) {
+  const linkIndex = findLastIndex(node.attributes, (attr) => attr.name && includes(linkAttributes, attr.name.name));
+  const foundExternalLink = linkIndex !== -1 && ((attr) => attr.value && attr.value.type === 'Literal' && /^(?:\w+:|\/\/)/.test(attr.value.value))(
+    node.attributes[linkIndex]);
+  return foundExternalLink || (warnOnSpreadAttributes && linkIndex < spreadAttributeIndex);
+}
+
+function hasDynamicLink(node, linkAttributes) {
+  const dynamicLinkIndex = findLastIndex(node.attributes, (attr) => attr.name
+    && includes(linkAttributes, attr.name.name)
+    && attr.value
+    && attr.value.type === 'JSXExpressionContainer');
+  if (dynamicLinkIndex !== -1) {
+    return true;
+  }
+}
+
+/**
+ * Get the string(s) from a value
+ * @param {ASTNode} value The AST node being checked.
+ * @param {ASTNode} targetValue The AST node being checked.
+ * @returns {string | string[] | null} The string value, or null if not a string.
+ */
+function getStringFromValue(value, targetValue) {
+  if (value) {
+    if (value.type === 'Literal') {
+      return value.value;
+    }
+    if (value.type === 'JSXExpressionContainer') {
+      if (value.expression.type === 'TemplateLiteral') {
+        return value.expression.quasis[0].value.cooked;
+      }
+      const expr = value.expression;
+      if (expr && expr.type === 'ConditionalExpression') {
+        const relValues = [expr.consequent.value, expr.alternate.value];
+        if (targetValue.type === 'JSXExpressionContainer' && targetValue.expression && targetValue.expression.type === 'ConditionalExpression') {
+          const targetTestCond = targetValue.expression.test.name;
+          const relTestCond = value.expression.test.name;
+          if (targetTestCond === relTestCond) {
+            const targetBlankIndex = [targetValue.expression.consequent.value, targetValue.expression.alternate.value].indexOf('_blank');
+            return relValues[targetBlankIndex];
+          }
+        }
+        return relValues;
+      }
+      return expr.value;
+    }
+  }
+  return null;
+}
+
+function hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttributeIndex) {
+  const relIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'rel'));
+  const targetIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXAttribute' && attr.name.name === 'target'));
+  if (relIndex === -1 || (warnOnSpreadAttributes && relIndex < spreadAttributeIndex)) {
+    return false;
+  }
+
+  const relAttribute = node.attributes[relIndex];
+  const targetAttributeValue = node.attributes[targetIndex] && node.attributes[targetIndex].value;
+  const value = getStringFromValue(relAttribute.value, targetAttributeValue);
+  return [].concat(value).every((item) => {
+    const tags = typeof item === 'string' ? item.toLowerCase().split(' ') : false;
+    const noreferrer = tags && tags.indexOf('noreferrer') >= 0;
+    if (noreferrer) {
+      return true;
+    }
+    const noopener = tags && tags.indexOf('noopener') >= 0;
+    return allowReferrer && noopener;
+  });
+}
+
+const messages = {
+  noTargetBlankWithoutNoreferrer: 'Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations',
+  noTargetBlankWithoutNoopener: 'Using target="_blank" without rel="noreferrer" or rel="noopener" (the former implies the latter and is preferred due to wider support) is a security risk: see https://mathiasbynens.github.io/rel-noopener/#recommendations',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    fixable: 'code',
+    docs: {
+      description: 'Disallow `target="_blank"` attribute without `rel="noreferrer"`',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('jsx-no-target-blank'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowReferrer: {
+          type: 'boolean',
+        },
+        enforceDynamicLinks: {
+          enum: ['always', 'never'],
+        },
+        warnOnSpreadAttributes: {
+          type: 'boolean',
+        },
+        links: {
+          type: 'boolean',
+          default: true,
+        },
+        forms: {
+          type: 'boolean',
+          default: false,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = Object.assign(
+      {
+        allowReferrer: false,
+        warnOnSpreadAttributes: false,
+        links: true,
+        forms: false,
+      },
+      context.options[0]
+    );
+    const allowReferrer = configuration.allowReferrer;
+    const warnOnSpreadAttributes = configuration.warnOnSpreadAttributes;
+    const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always';
+    const linkComponents = linkComponentsUtil.getLinkComponents(context);
+    const formComponents = linkComponentsUtil.getFormComponents(context);
+
+    return {
+      JSXOpeningElement(node) {
+        const targetIndex = findLastIndex(node.attributes, (attr) => attr.name && attr.name.name === 'target');
+        const spreadAttributeIndex = findLastIndex(node.attributes, (attr) => (attr.type === 'JSXSpreadAttribute'));
+
+        if (linkComponents.has(node.name.name)) {
+          if (!attributeValuePossiblyBlank(node.attributes[targetIndex])) {
+            const hasSpread = spreadAttributeIndex >= 0;
+
+            if (warnOnSpreadAttributes && hasSpread) {
+              // continue to check below
+            } else if ((hasSpread && targetIndex < spreadAttributeIndex) || !hasSpread || !warnOnSpreadAttributes) {
+              return;
+            }
+          }
+
+          const linkAttributes = linkComponents.get(node.name.name);
+          const hasDangerousLink = hasExternalLink(node, linkAttributes, warnOnSpreadAttributes, spreadAttributeIndex)
+            || (enforceDynamicLinks === 'always' && hasDynamicLink(node, linkAttributes));
+          if (hasDangerousLink && !hasSecureRel(node, allowReferrer, warnOnSpreadAttributes, spreadAttributeIndex)) {
+            const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer';
+            const relValue = allowReferrer ? 'noopener' : 'noreferrer';
+            report(context, messages[messageId], messageId, {
+              node,
+              fix(fixer) {
+                // eslint 5 uses `node.attributes`; eslint 6+ uses `node.parent.attributes`
+                const nodeWithAttrs = node.parent.attributes ? node.parent : node;
+                // eslint 5 does not provide a `name` property on JSXSpreadElements
+                const relAttribute = nodeWithAttrs.attributes.find((attr) => attr.name && attr.name.name === 'rel');
+
+                if (targetIndex < spreadAttributeIndex || (spreadAttributeIndex >= 0 && !relAttribute)) {
+                  return null;
+                }
+
+                if (!relAttribute) {
+                  return fixer.insertTextAfter(nodeWithAttrs.attributes.slice(-1)[0], ` rel="${relValue}"`);
+                }
+
+                if (!relAttribute.value) {
+                  return fixer.insertTextAfter(relAttribute, `="${relValue}"`);
+                }
+
+                if (relAttribute.value.type === 'Literal') {
+                  const parts = relAttribute.value.value
+                    .split('noreferrer')
+                    .filter(Boolean);
+                  return fixer.replaceText(relAttribute.value, `"${parts.concat('noreferrer').join(' ')}"`);
+                }
+
+                if (relAttribute.value.type === 'JSXExpressionContainer') {
+                  if (relAttribute.value.expression.type === 'Literal') {
+                    if (typeof relAttribute.value.expression.value === 'string') {
+                      const parts = relAttribute.value.expression.value
+                        .split('noreferrer')
+                        .filter(Boolean);
+                      return fixer.replaceText(relAttribute.value.expression, `"${parts.concat('noreferrer').join(' ')}"`);
+                    }
+
+                    // for undefined, boolean, number, symbol, bigint, and null
+                    return fixer.replaceText(relAttribute.value, '"noreferrer"');
+                  }
+                }
+
+                return null;
+              },
+            });
+          }
+        }
+        if (formComponents.has(node.name.name)) {
+          if (!attributeValuePossiblyBlank(node.attributes[targetIndex])) {
+            const hasSpread = spreadAttributeIndex >= 0;
+
+            if (warnOnSpreadAttributes && hasSpread) {
+              // continue to check below
+            } else if (
+              (hasSpread && targetIndex < spreadAttributeIndex)
+              || !hasSpread
+              || !warnOnSpreadAttributes
+            ) {
+              return;
+            }
+          }
+
+          if (!configuration.forms || hasSecureRel(node)) {
+            return;
+          }
+
+          const formAttributes = formComponents.get(node.name.name);
+
+          if (
+            hasExternalLink(node, formAttributes)
+            || (enforceDynamicLinks === 'always' && hasDynamicLink(node, formAttributes))
+          ) {
+            const messageId = allowReferrer ? 'noTargetBlankWithoutNoopener' : 'noTargetBlankWithoutNoreferrer';
+            report(context, messages[messageId], messageId, {
+              node,
+            });
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-undef.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-undef.d.ts","sourceRoot":"","sources":["jsx-no-undef.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-undef.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Disallow undeclared variables in JSX
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  undefined: '\'{{identifier}}\' is not defined.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow undeclared variables in JSX',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('jsx-no-undef'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowGlobals: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const config = context.options[0] || {};
+    const allowGlobals = config.allowGlobals || false;
+
+    /**
+     * Compare an identifier with the variables declared in the scope
+     * @param {ASTNode} node - Identifier or JSXIdentifier node
+     * @returns {void}
+     */
+    function checkIdentifierInJSX(node) {
+      let scope = eslintUtil.getScope(context, node);
+      const sourceCode = eslintUtil.getSourceCode(context);
+      const sourceType = sourceCode.ast.sourceType;
+      const scopeUpperBound = !allowGlobals && sourceType === 'module' ? 'module' : 'global';
+      let variables = scope.variables;
+      let i;
+      let len;
+
+      // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX)
+      if (node.name === 'this') {
+        return;
+      }
+
+      while (scope.type !== scopeUpperBound && scope.type !== 'global') {
+        scope = scope.upper;
+        variables = scope.variables.concat(variables);
+      }
+      if (scope.childScopes.length) {
+        variables = scope.childScopes[0].variables.concat(variables);
+        // Temporary fix for babel-eslint
+        if (scope.childScopes[0].childScopes.length) {
+          variables = scope.childScopes[0].childScopes[0].variables.concat(variables);
+        }
+      }
+
+      for (i = 0, len = variables.length; i < len; i++) {
+        if (variables[i].name === node.name) {
+          return;
+        }
+      }
+
+      report(context, messages.undefined, 'undefined', {
+        node,
+        data: {
+          identifier: node.name,
+        },
+      });
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        switch (node.name.type) {
+          case 'JSXIdentifier':
+            if (jsxUtil.isDOMComponent(node)) {
+              return;
+            }
+            node = node.name;
+            break;
+          case 'JSXMemberExpression':
+            node = node.name;
+            do {
+              node = node.object;
+            } while (node && node.type !== 'JSXIdentifier');
+            break;
+          case 'JSXNamespacedName':
+            return;
+          default:
+            break;
+        }
+        checkIdentifierInJSX(node);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-no-useless-fragment.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-no-useless-fragment.d.ts","sourceRoot":"","sources":["jsx-no-useless-fragment.js"],"names":[],"mappings":"wBAsFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-useless-fragment.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,259 @@
+/**
+ * @fileoverview Disallow useless fragments
+ */
+
+'use strict';
+
+const arrayIncludes = require('array-includes');
+
+const pragmaUtil = require('../util/pragma');
+const astUtil = require('../util/ast');
+const jsxUtil = require('../util/jsx');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+function isJSXText(node) {
+  return !!node && (node.type === 'JSXText' || node.type === 'Literal');
+}
+
+/**
+ * @param {string} text
+ * @returns {boolean}
+ */
+function isOnlyWhitespace(text) {
+  return text.trim().length === 0;
+}
+
+/**
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isNonspaceJSXTextOrJSXCurly(node) {
+  return (isJSXText(node) && !isOnlyWhitespace(node.raw)) || node.type === 'JSXExpressionContainer';
+}
+
+/**
+ * Somehow fragment like this is useful: <Foo content={<>ee eeee eeee ...</>} />
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isFragmentWithOnlyTextAndIsNotChild(node) {
+  return node.children.length === 1
+    && isJSXText(node.children[0])
+    && !(node.parent.type === 'JSXElement' || node.parent.type === 'JSXFragment');
+}
+
+/**
+ * @param {string} text
+ * @returns {string}
+ */
+function trimLikeReact(text) {
+  const leadingSpaces = /^\s*/.exec(text)[0];
+  const trailingSpaces = /\s*$/.exec(text)[0];
+
+  const start = arrayIncludes(leadingSpaces, '\n') ? leadingSpaces.length : 0;
+  const end = arrayIncludes(trailingSpaces, '\n') ? text.length - trailingSpaces.length : text.length;
+
+  return text.slice(start, end);
+}
+
+/**
+ * Test if node is like `<Fragment key={_}>_</Fragment>`
+ * @param {JSXElement} node
+ * @returns {boolean}
+ */
+function isKeyedElement(node) {
+  return node.type === 'JSXElement'
+    && node.openingElement.attributes
+    && node.openingElement.attributes.some(jsxUtil.isJSXAttributeKey);
+}
+
+/**
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function containsCallExpression(node) {
+  return node
+    && node.type === 'JSXExpressionContainer'
+    && astUtil.isCallExpression(node.expression);
+}
+
+const messages = {
+  NeedsMoreChildren: 'Fragments should contain more than one child - otherwise, there’s no need for a Fragment at all.',
+  ChildOfHtmlElement: 'Passing a fragment to an HTML element is useless.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    fixable: 'code',
+    docs: {
+      description: 'Disallow unnecessary fragments',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('jsx-no-useless-fragment'),
+    },
+    messages,
+    schema: [{
+      type: 'object',
+      properties: {
+        allowExpressions: {
+          type: 'boolean',
+        },
+      },
+    }],
+  },
+
+  create(context) {
+    const config = context.options[0] || {};
+    const allowExpressions = config.allowExpressions || false;
+
+    const reactPragma = pragmaUtil.getFromContext(context);
+    const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
+
+    /**
+     * Test whether a node is an padding spaces trimmed by react runtime.
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    function isPaddingSpaces(node) {
+      return isJSXText(node)
+        && isOnlyWhitespace(node.raw)
+        && arrayIncludes(node.raw, '\n');
+    }
+
+    function isFragmentWithSingleExpression(node) {
+      const children = node && node.children.filter((child) => !isPaddingSpaces(child));
+      return (
+        children
+        && children.length === 1
+        && children[0].type === 'JSXExpressionContainer'
+      );
+    }
+
+    /**
+     * Test whether a JSXElement has less than two children, excluding paddings spaces.
+     * @param {JSXElement|JSXFragment} node
+     * @returns {boolean}
+     */
+    function hasLessThanTwoChildren(node) {
+      if (!node || !node.children) {
+        return true;
+      }
+
+      /** @type {ASTNode[]} */
+      const nonPaddingChildren = node.children.filter(
+        (child) => !isPaddingSpaces(child)
+      );
+
+      if (nonPaddingChildren.length < 2) {
+        return !containsCallExpression(nonPaddingChildren[0]);
+      }
+    }
+
+    /**
+     * @param {JSXElement|JSXFragment} node
+     * @returns {boolean}
+     */
+    function isChildOfHtmlElement(node) {
+      return node.parent.type === 'JSXElement'
+        && node.parent.openingElement.name.type === 'JSXIdentifier'
+        && /^[a-z]+$/.test(node.parent.openingElement.name.name);
+    }
+
+    /**
+     * @param {JSXElement|JSXFragment} node
+     * @return {boolean}
+     */
+    function isChildOfComponentElement(node) {
+      return node.parent.type === 'JSXElement'
+        && !isChildOfHtmlElement(node)
+        && !jsxUtil.isFragment(node.parent, reactPragma, fragmentPragma);
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    function canFix(node) {
+      // Not safe to fix fragments without a jsx parent.
+      if (!(node.parent.type === 'JSXElement' || node.parent.type === 'JSXFragment')) {
+        // const a = <></>
+        if (node.children.length === 0) {
+          return false;
+        }
+
+        // const a = <>cat {meow}</>
+        if (node.children.some(isNonspaceJSXTextOrJSXCurly)) {
+          return false;
+        }
+      }
+
+      // Not safe to fix `<Eeee><>foo</></Eeee>` because `Eeee` might require its children be a ReactElement.
+      if (isChildOfComponentElement(node)) {
+        return false;
+      }
+
+      // old TS parser can't handle this one
+      if (node.type === 'JSXFragment' && (!node.openingFragment || !node.closingFragment)) {
+        return false;
+      }
+
+      return true;
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {Function | undefined}
+     */
+    function getFix(node) {
+      if (!canFix(node)) {
+        return undefined;
+      }
+
+      return function fix(fixer) {
+        const opener = node.type === 'JSXFragment' ? node.openingFragment : node.openingElement;
+        const closer = node.type === 'JSXFragment' ? node.closingFragment : node.closingElement;
+
+        const childrenText = opener.selfClosing ? '' : getText(context).slice(opener.range[1], closer.range[0]);
+
+        return fixer.replaceText(node, trimLikeReact(childrenText));
+      };
+    }
+
+    function checkNode(node) {
+      if (isKeyedElement(node)) {
+        return;
+      }
+
+      if (
+        hasLessThanTwoChildren(node)
+        && !isFragmentWithOnlyTextAndIsNotChild(node)
+        && !(allowExpressions && isFragmentWithSingleExpression(node))
+      ) {
+        report(context, messages.NeedsMoreChildren, 'NeedsMoreChildren', {
+          node,
+          fix: getFix(node),
+        });
+      }
+
+      if (isChildOfHtmlElement(node)) {
+        report(context, messages.ChildOfHtmlElement, 'ChildOfHtmlElement', {
+          node,
+          fix: getFix(node),
+        });
+      }
+    }
+
+    return {
+      JSXElement(node) {
+        if (jsxUtil.isFragment(node, reactPragma, fragmentPragma)) {
+          checkNode(node);
+        }
+      },
+      JSXFragment: checkNode,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-one-expression-per-line.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-one-expression-per-line.d.ts","sourceRoot":"","sources":["jsx-one-expression-per-line.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-one-expression-per-line.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,249 @@
+/**
+ * @fileoverview Limit to one expression per line in JSX
+ * @author Mark Ivan Allen <Vydia.com>
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const optionDefaults = {
+  allow: 'none',
+};
+
+const messages = {
+  moveToNewLine: '`{{descriptor}}` must be placed on a new line',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Require one JSX element per line',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-one-expression-per-line'),
+    },
+    fixable: 'whitespace',
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allow: {
+            enum: ['none', 'literal', 'single-child', 'non-jsx'],
+          },
+        },
+        default: optionDefaults,
+        additionalProperties: false,
+      },
+    ],
+  },
+
+  create(context) {
+    const options = Object.assign({}, optionDefaults, context.options[0]);
+
+    function nodeKey(node) {
+      return `${node.loc.start.line},${node.loc.start.column}`;
+    }
+
+    /**
+     * @param {ASTNode} n
+     * @returns {string}
+     */
+    function nodeDescriptor(n) {
+      return n.openingElement ? n.openingElement.name.name : getText(context, n).replace(/\n/g, '');
+    }
+
+    function handleJSX(node) {
+      const children = node.children;
+
+      if (!children || !children.length) {
+        return;
+      }
+
+      if (
+        options.allow === 'non-jsx'
+        && !children.find((child) => (child.type === 'JSXFragment' || child.type === 'JSXElement'))
+      ) {
+        return;
+      }
+
+      const openingElement = node.openingElement || node.openingFragment;
+      const closingElement = node.closingElement || node.closingFragment;
+      const openingElementStartLine = openingElement.loc.start.line;
+      const openingElementEndLine = openingElement.loc.end.line;
+      const closingElementStartLine = closingElement.loc.start.line;
+      const closingElementEndLine = closingElement.loc.end.line;
+
+      if (children.length === 1) {
+        const child = children[0];
+        if (
+          openingElementStartLine === openingElementEndLine
+          && openingElementEndLine === closingElementStartLine
+          && closingElementStartLine === closingElementEndLine
+          && closingElementEndLine === child.loc.start.line
+          && child.loc.start.line === child.loc.end.line
+        ) {
+          if (
+            options.allow === 'single-child'
+            || (options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText'))
+          ) {
+            return;
+          }
+        }
+      }
+
+      const childrenGroupedByLine = {};
+      const fixDetailsByNode = {};
+
+      children.forEach((child) => {
+        let countNewLinesBeforeContent = 0;
+        let countNewLinesAfterContent = 0;
+
+        if (child.type === 'Literal' || child.type === 'JSXText') {
+          if (jsxUtil.isWhiteSpaces(child.raw)) {
+            return;
+          }
+
+          countNewLinesBeforeContent = (child.raw.match(/^\s*\n/g) || []).length;
+          countNewLinesAfterContent = (child.raw.match(/\n\s*$/g) || []).length;
+        }
+
+        const startLine = child.loc.start.line + countNewLinesBeforeContent;
+        const endLine = child.loc.end.line - countNewLinesAfterContent;
+
+        if (startLine === endLine) {
+          if (!childrenGroupedByLine[startLine]) {
+            childrenGroupedByLine[startLine] = [];
+          }
+          childrenGroupedByLine[startLine].push(child);
+        } else {
+          if (!childrenGroupedByLine[startLine]) {
+            childrenGroupedByLine[startLine] = [];
+          }
+          childrenGroupedByLine[startLine].push(child);
+          if (!childrenGroupedByLine[endLine]) {
+            childrenGroupedByLine[endLine] = [];
+          }
+          childrenGroupedByLine[endLine].push(child);
+        }
+      });
+
+      Object.keys(childrenGroupedByLine).forEach((_line) => {
+        const line = parseInt(_line, 10);
+        const firstIndex = 0;
+        const lastIndex = childrenGroupedByLine[line].length - 1;
+
+        childrenGroupedByLine[line].forEach((child, i) => {
+          let prevChild;
+          let nextChild;
+
+          if (i === firstIndex) {
+            if (line === openingElementEndLine) {
+              prevChild = openingElement;
+            }
+          } else {
+            prevChild = childrenGroupedByLine[line][i - 1];
+          }
+
+          if (i === lastIndex) {
+            if (line === closingElementStartLine) {
+              nextChild = closingElement;
+            }
+          } else {
+            // We don't need to append a trailing because the next child will prepend a leading.
+            // nextChild = childrenGroupedByLine[line][i + 1];
+          }
+
+          function spaceBetweenPrev() {
+            return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && / $/.test(prevChild.raw))
+              || ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw))
+              || getSourceCode(context).isSpaceBetweenTokens(prevChild, child);
+          }
+
+          function spaceBetweenNext() {
+            return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && /^ /.test(nextChild.raw))
+              || ((child.type === 'Literal' || child.type === 'JSXText') && / $/.test(child.raw))
+              || getSourceCode(context).isSpaceBetweenTokens(child, nextChild);
+          }
+
+          if (!prevChild && !nextChild) {
+            return;
+          }
+
+          const source = getText(context, child);
+          const leadingSpace = !!(prevChild && spaceBetweenPrev());
+          const trailingSpace = !!(nextChild && spaceBetweenNext());
+          const leadingNewLine = !!prevChild;
+          const trailingNewLine = !!nextChild;
+
+          const key = nodeKey(child);
+
+          if (!fixDetailsByNode[key]) {
+            fixDetailsByNode[key] = {
+              node: child,
+              source,
+              descriptor: nodeDescriptor(child),
+            };
+          }
+
+          if (leadingSpace) {
+            fixDetailsByNode[key].leadingSpace = true;
+          }
+          if (leadingNewLine) {
+            fixDetailsByNode[key].leadingNewLine = true;
+          }
+          if (trailingNewLine) {
+            fixDetailsByNode[key].trailingNewLine = true;
+          }
+          if (trailingSpace) {
+            fixDetailsByNode[key].trailingSpace = true;
+          }
+        });
+      });
+
+      Object.keys(fixDetailsByNode).forEach((key) => {
+        const details = fixDetailsByNode[key];
+
+        const nodeToReport = details.node;
+        const descriptor = details.descriptor;
+        const source = details.source.replace(/(^ +| +(?=\n)*$)/g, '');
+
+        const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : '';
+        const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : '';
+        const leadingNewLineString = details.leadingNewLine ? '\n' : '';
+        const trailingNewLineString = details.trailingNewLine ? '\n' : '';
+
+        const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`;
+
+        report(context, messages.moveToNewLine, 'moveToNewLine', {
+          node: nodeToReport,
+          data: {
+            descriptor,
+          },
+          fix(fixer) {
+            return fixer.replaceText(nodeToReport, replaceText);
+          },
+        });
+      });
+    }
+
+    return {
+      JSXElement: handleJSX,
+      JSXFragment: handleJSX,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-pascal-case.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-pascal-case.d.ts","sourceRoot":"","sources":["jsx-pascal-case.js"],"names":[],"mappings":"wBA8EW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-pascal-case.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,164 @@
+/**
+ * @fileoverview Enforce PascalCase for user-defined JSX components
+ * @author Jake Marsh
+ */
+
+'use strict';
+
+const elementType = require('jsx-ast-utils/elementType');
+const minimatch = require('minimatch');
+const docsUrl = require('../util/docsUrl');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+
+function testDigit(char) {
+  const charCode = char.charCodeAt(0);
+  return charCode >= 48 && charCode <= 57;
+}
+
+function testUpperCase(char) {
+  const upperCase = char.toUpperCase();
+  return char === upperCase && upperCase !== char.toLowerCase();
+}
+
+function testLowerCase(char) {
+  const lowerCase = char.toLowerCase();
+  return char === lowerCase && lowerCase !== char.toUpperCase();
+}
+
+function testPascalCase(name) {
+  if (!testUpperCase(name.charAt(0))) {
+    return false;
+  }
+  const anyNonAlphaNumeric = Array.prototype.some.call(
+    name.slice(1),
+    (char) => char.toLowerCase() === char.toUpperCase() && !testDigit(char)
+  );
+  if (anyNonAlphaNumeric) {
+    return false;
+  }
+  return Array.prototype.some.call(
+    name.slice(1),
+    (char) => testLowerCase(char) || testDigit(char)
+  );
+}
+
+function testAllCaps(name) {
+  const firstChar = name.charAt(0);
+  if (!(testUpperCase(firstChar) || testDigit(firstChar))) {
+    return false;
+  }
+  for (let i = 1; i < name.length - 1; i += 1) {
+    const char = name.charAt(i);
+    if (!(testUpperCase(char) || testDigit(char) || char === '_')) {
+      return false;
+    }
+  }
+  const lastChar = name.charAt(name.length - 1);
+  if (!(testUpperCase(lastChar) || testDigit(lastChar))) {
+    return false;
+  }
+  return true;
+}
+
+function ignoreCheck(ignore, name) {
+  return ignore.some(
+    (entry) => name === entry || minimatch(name, entry, { noglobstar: true })
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  usePascalCase: 'Imported JSX component {{name}} must be in PascalCase',
+  usePascalOrSnakeCase: 'Imported JSX component {{name}} must be in PascalCase or SCREAMING_SNAKE_CASE',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce PascalCase for user-defined JSX components',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-pascal-case'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowAllCaps: {
+          type: 'boolean',
+        },
+        allowLeadingUnderscore: {
+          type: 'boolean',
+        },
+        allowNamespace: {
+          type: 'boolean',
+        },
+        ignore: {
+          items: [
+            {
+              type: 'string',
+            },
+          ],
+          minItems: 0,
+          type: 'array',
+          uniqueItems: true,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const allowAllCaps = configuration.allowAllCaps || false;
+    const allowLeadingUnderscore = configuration.allowLeadingUnderscore || false;
+    const allowNamespace = configuration.allowNamespace || false;
+    const ignore = configuration.ignore || [];
+
+    return {
+      JSXOpeningElement(node) {
+        const isCompatTag = jsxUtil.isDOMComponent(node);
+        if (isCompatTag) return undefined;
+
+        const name = elementType(node);
+        let checkNames = [name];
+        let index = 0;
+
+        if (name.lastIndexOf(':') > -1) {
+          checkNames = name.split(':');
+        } else if (name.lastIndexOf('.') > -1) {
+          checkNames = name.split('.');
+        }
+
+        do {
+          const splitName = checkNames[index];
+          if (splitName.length === 1) return undefined;
+          const isIgnored = ignoreCheck(ignore, splitName);
+
+          const checkName = allowLeadingUnderscore && splitName.startsWith('_') ? splitName.slice(1) : splitName;
+          const isPascalCase = testPascalCase(checkName);
+          const isAllowedAllCaps = allowAllCaps && testAllCaps(checkName);
+
+          if (!isPascalCase && !isAllowedAllCaps && !isIgnored) {
+            const messageId = allowAllCaps ? 'usePascalOrSnakeCase' : 'usePascalCase';
+            report(context, messages[messageId], messageId, {
+              node,
+              data: {
+                name: splitName,
+              },
+            });
+            break;
+          }
+          index += 1;
+        } while (index < checkNames.length && !allowNamespace);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-props-no-multi-spaces.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-props-no-multi-spaces.d.ts","sourceRoot":"","sources":["jsx-props-no-multi-spaces.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-multi-spaces.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,144 @@
+/**
+ * @fileoverview Disallow multiple spaces between inline JSX props
+ * @author Adrian Moennich
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const report = require('../util/report');
+const propsUtil = require('../util/props');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”',
+  onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow multiple spaces between inline JSX props',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-props-no-multi-spaces'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    const sourceCode = getSourceCode(context);
+
+    function getPropName(propNode) {
+      switch (propNode.type) {
+        case 'JSXSpreadAttribute':
+          return getText(context, propNode.argument);
+        case 'JSXIdentifier':
+          return propNode.name;
+        case 'JSXMemberExpression':
+          return `${getPropName(propNode.object)}.${propNode.property.name}`;
+        default:
+          return propNode.name
+            ? propNode.name.name
+            : `${getText(context, propNode.object)}.${propNode.property.name}`; // needed for typescript-eslint parser
+      }
+    }
+
+    // First and second must be adjacent nodes
+    function hasEmptyLines(first, second) {
+      const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : [];
+      const nodes = [].concat(first, comments, second);
+
+      for (let i = 1; i < nodes.length; i += 1) {
+        const prev = nodes[i - 1];
+        const curr = nodes[i];
+        if (curr.loc.start.line - prev.loc.end.line >= 2) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    function checkSpacing(prev, node) {
+      if (hasEmptyLines(prev, node)) {
+        report(context, messages.noLineGap, 'noLineGap', {
+          node,
+          data: {
+            prop1: getPropName(prev),
+            prop2: getPropName(node),
+          },
+        });
+      }
+
+      if (prev.loc.end.line !== node.loc.end.line) {
+        return;
+      }
+
+      const between = getSourceCode(context).text.slice(prev.range[1], node.range[0]);
+
+      if (between !== ' ') {
+        report(context, messages.onlyOneSpace, 'onlyOneSpace', {
+          node,
+          data: {
+            prop1: getPropName(prev),
+            prop2: getPropName(node),
+          },
+          fix(fixer) {
+            return fixer.replaceTextRange([prev.range[1], node.range[0]], ' ');
+          },
+        });
+      }
+    }
+
+    function containsGenericType(node) {
+      const nodeTypeArguments = propsUtil.getTypeArguments(node);
+      if (typeof nodeTypeArguments === 'undefined') {
+        return false;
+      }
+
+      return nodeTypeArguments.type === 'TSTypeParameterInstantiation';
+    }
+
+    function getGenericNode(node) {
+      const name = node.name;
+      if (containsGenericType(node)) {
+        const nodeTypeArguments = propsUtil.getTypeArguments(node);
+
+        return Object.assign(
+          {},
+          node,
+          {
+            range: [
+              name.range[0],
+              nodeTypeArguments.range[1],
+            ],
+          }
+        );
+      }
+
+      return name;
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        node.attributes.reduce((prev, prop) => {
+          checkSpacing(prev, prop);
+          return prop;
+        }, getGenericNode(node));
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-props-no-spread-multi.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-props-no-spread-multi.d.ts","sourceRoot":"","sources":["jsx-props-no-spread-multi.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spread-multi.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Prevent JSX prop spreading the same expression multiple times
+ * @author Simon Schick
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noMultiSpreading: 'Spreading the same expression multiple times is forbidden',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow JSX prop spreading the same identifier multiple times',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('jsx-props-no-spread-multi'),
+    },
+    messages,
+  },
+
+  create(context) {
+    return {
+      JSXOpeningElement(node) {
+        const spreads = node.attributes.filter(
+          (attr) => attr.type === 'JSXSpreadAttribute'
+          && attr.argument.type === 'Identifier'
+        );
+        if (spreads.length < 2) {
+          return;
+        }
+        // We detect duplicate expressions by their identifier
+        const identifierNames = new Set();
+        spreads.forEach((spread) => {
+          if (identifierNames.has(spread.argument.name)) {
+            report(context, messages.noMultiSpreading, 'noMultiSpreading', {
+              node: spread,
+            });
+          }
+          identifierNames.add(spread.argument.name);
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-props-no-spreading.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-props-no-spreading.d.ts","sourceRoot":"","sources":["jsx-props-no-spreading.js"],"names":[],"mappings":"wBAwCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-props-no-spreading.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,145 @@
+/**
+ * @fileoverview Prevent JSX prop spreading
+ * @author Ashish Gambhir
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const OPTIONS = { ignore: 'ignore', enforce: 'enforce' };
+const DEFAULTS = {
+  html: OPTIONS.enforce,
+  custom: OPTIONS.enforce,
+  explicitSpread: OPTIONS.enforce,
+  exceptions: [],
+};
+
+const isException = (tag, allExceptions) => allExceptions.indexOf(tag) !== -1;
+const isProperty = (property) => property.type === 'Property';
+const getTagNameFromMemberExpression = (node) => {
+  if (node.property.parent) {
+    return `${node.property.parent.object.name}.${node.property.name}`;
+  }
+  // for eslint 3
+  return `${node.object.name}.${node.property.name}`;
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noSpreading: 'Prop spreading is forbidden',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow JSX prop spreading',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('jsx-props-no-spreading'),
+    },
+
+    messages,
+
+    schema: [{
+      allOf: [{
+        type: 'object',
+        properties: {
+          html: {
+            enum: [OPTIONS.enforce, OPTIONS.ignore],
+          },
+          custom: {
+            enum: [OPTIONS.enforce, OPTIONS.ignore],
+          },
+          explicitSpread: {
+            enum: [OPTIONS.enforce, OPTIONS.ignore],
+          },
+          exceptions: {
+            type: 'array',
+            items: {
+              type: 'string',
+              uniqueItems: true,
+            },
+          },
+        },
+      }, {
+        not: {
+          type: 'object',
+          required: ['html', 'custom'],
+          properties: {
+            html: {
+              enum: [OPTIONS.ignore],
+            },
+            custom: {
+              enum: [OPTIONS.ignore],
+            },
+            exceptions: {
+              type: 'array',
+              minItems: 0,
+              maxItems: 0,
+            },
+          },
+        },
+      }],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const ignoreHtmlTags = (configuration.html || DEFAULTS.html) === OPTIONS.ignore;
+    const ignoreCustomTags = (configuration.custom || DEFAULTS.custom) === OPTIONS.ignore;
+    const ignoreExplicitSpread = (configuration.explicitSpread || DEFAULTS.explicitSpread) === OPTIONS.ignore;
+    const exceptions = configuration.exceptions || DEFAULTS.exceptions;
+    return {
+      JSXSpreadAttribute(node) {
+        const jsxOpeningElement = node.parent.name;
+        const type = jsxOpeningElement.type;
+
+        let tagName;
+        if (type === 'JSXIdentifier') {
+          tagName = jsxOpeningElement.name;
+        } else if (type === 'JSXMemberExpression') {
+          tagName = getTagNameFromMemberExpression(jsxOpeningElement);
+        } else {
+          tagName = undefined;
+        }
+
+        const isHTMLTag = tagName && tagName[0] !== tagName[0].toUpperCase();
+        const isCustomTag = tagName && (tagName[0] === tagName[0].toUpperCase() || tagName.includes('.'));
+        if (
+          isHTMLTag
+          && ((ignoreHtmlTags && !isException(tagName, exceptions))
+          || (!ignoreHtmlTags && isException(tagName, exceptions)))
+        ) {
+          return;
+        }
+        if (
+          isCustomTag
+          && ((ignoreCustomTags && !isException(tagName, exceptions))
+          || (!ignoreCustomTags && isException(tagName, exceptions)))
+        ) {
+          return;
+        }
+        if (
+          ignoreExplicitSpread
+          && node.argument.type === 'ObjectExpression'
+          && node.argument.properties.every(isProperty)
+        ) {
+          return;
+        }
+        report(context, messages.noSpreading, 'noSpreading', {
+          node,
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-sort-default-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-sort-default-props.d.ts","sourceRoot":"","sources":["jsx-sort-default-props.js"],"names":[],"mappings":"wBA2BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,195 @@
+/**
+ * @fileoverview Enforce default props alphabetical sorting
+ * @author Vladimir Kattsov
+ * @deprecated
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const log = require('../util/log');
+const eslintUtil = require('../util/eslint');
+
+const getFirstTokens = eslintUtil.getFirstTokens;
+const getText = eslintUtil.getText;
+
+let isWarnedForDeprecation = false;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  propsNotSorted: 'Default prop types declarations should be sorted alphabetically',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    deprecated: true,
+    replacedBy: ['sort-default-props'],
+    docs: {
+      description: 'Enforce defaultProps declarations alphabetical sorting',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-sort-default-props'),
+    },
+    // fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreCase: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const ignoreCase = configuration.ignoreCase || false;
+
+    /**
+     * Get properties name
+     * @param {Object} node - Property.
+     * @returns {string} Property name.
+     */
+    function getPropertyName(node) {
+      if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
+        return node.key.name;
+      }
+      if (node.type === 'MemberExpression') {
+        return node.property.name;
+      // Special case for class properties
+      // (babel-eslint@5 does not expose property name so we have to rely on tokens)
+      }
+      if (node.type === 'ClassProperty') {
+        const tokens = getFirstTokens(context, node, 2);
+        return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
+      }
+      return '';
+    }
+
+    /**
+     * Checks if the Identifier node passed in looks like a defaultProps declaration.
+     * @param   {ASTNode}  node The node to check. Must be an Identifier node.
+     * @returns {boolean}       `true` if the node is a defaultProps declaration, `false` if not
+     */
+    function isDefaultPropsDeclaration(node) {
+      const propName = getPropertyName(node);
+      return (propName === 'defaultProps' || propName === 'getDefaultProps');
+    }
+
+    function getKey(node) {
+      return getText(context, node.key || node.argument);
+    }
+
+    /**
+     * Find a variable by name in the current scope.
+     * @param  {ASTNode} node The node to look for.
+     * @param  {string} name Name of the variable to look for.
+     * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
+     */
+    function findVariableByName(node, name) {
+      const variable = variableUtil
+        .getVariableFromContext(context, node, name);
+
+      if (!variable || !variable.defs[0] || !variable.defs[0].node) {
+        return null;
+      }
+
+      if (variable.defs[0].node.type === 'TypeAlias') {
+        return variable.defs[0].node.right;
+      }
+
+      return variable.defs[0].node.init;
+    }
+
+    /**
+     * Checks if defaultProps declarations are sorted
+     * @param {Array} declarations The array of AST nodes being checked.
+     * @returns {void}
+     */
+    function checkSorted(declarations) {
+      // function fix(fixer) {
+      //   return propTypesSortUtil.fixPropTypesSort(context, fixer, declarations, ignoreCase);
+      // }
+
+      declarations.reduce((prev, curr, idx, decls) => {
+        if (/Spread(?:Property|Element)$/.test(curr.type)) {
+          return decls[idx + 1];
+        }
+
+        let prevPropName = getKey(prev);
+        let currentPropName = getKey(curr);
+
+        if (ignoreCase) {
+          prevPropName = prevPropName.toLowerCase();
+          currentPropName = currentPropName.toLowerCase();
+        }
+
+        if (currentPropName < prevPropName) {
+          report(context, messages.propsNotSorted, 'propsNotSorted', {
+            node: curr,
+            // fix
+          });
+
+          return prev;
+        }
+
+        return curr;
+      }, declarations[0]);
+    }
+
+    function checkNode(node) {
+      if (!node) {
+        return;
+      }
+      if (node.type === 'ObjectExpression') {
+        checkSorted(node.properties);
+      } else if (node.type === 'Identifier') {
+        const propTypesObject = findVariableByName(node, node.name);
+        if (propTypesObject && propTypesObject.properties) {
+          checkSorted(propTypesObject.properties);
+        }
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public API
+    // --------------------------------------------------------------------------
+
+    return {
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!isDefaultPropsDeclaration(node)) {
+          return;
+        }
+
+        checkNode(node.value);
+      },
+
+      MemberExpression(node) {
+        if (!isDefaultPropsDeclaration(node)) {
+          return;
+        }
+
+        checkNode('right' in node.parent && node.parent.right);
+      },
+
+      Program() {
+        if (isWarnedForDeprecation) {
+          return;
+        }
+
+        log('The react/jsx-sort-default-props rule is deprecated. It has been renamed to `react/sort-default-props`.');
+        isWarnedForDeprecation = true;
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-sort-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-sort-props.d.ts","sourceRoot":"","sources":["jsx-sort-props.js"],"names":[],"mappings":"wBAoVW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-sort-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,531 @@
+/**
+ * @fileoverview Enforce props alphabetical sorting
+ * @author Ilya Volodin, Yannick Croissant
+ */
+
+'use strict';
+
+const propName = require('jsx-ast-utils/propName');
+const includes = require('array-includes');
+const toSorted = require('array.prototype.tosorted');
+
+const docsUrl = require('../util/docsUrl');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+const propTypesSortUtil = require('../util/propTypesSort');
+const eslintUtil = require('../util/eslint');
+
+const getText = eslintUtil.getText;
+const getSourceCode = eslintUtil.getSourceCode;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function isMultilineProp(node) {
+  return node.loc.start.line !== node.loc.end.line;
+}
+
+const messages = {
+  noUnreservedProps: 'A customized reserved first list must only contain a subset of React reserved props. Remove: {{unreservedWords}}',
+  listIsEmpty: 'A customized reserved first list must not be empty',
+  listReservedPropsFirst: 'Reserved props must be listed before all other props',
+  listCallbacksLast: 'Callbacks must be listed after all other props',
+  listShorthandFirst: 'Shorthand props must be listed before all other props',
+  listShorthandLast: 'Shorthand props must be listed after all other props',
+  listMultilineFirst: 'Multiline props must be listed before all other props',
+  listMultilineLast: 'Multiline props must be listed after all other props',
+  sortPropsByAlpha: 'Props should be sorted alphabetically',
+};
+
+const RESERVED_PROPS_LIST = [
+  'children',
+  'dangerouslySetInnerHTML',
+  'key',
+  'ref',
+];
+
+function isReservedPropName(name, list) {
+  return list.indexOf(name) >= 0;
+}
+
+let attributeMap;
+// attributeMap = { end: endrange, hasComment: true||false if comment in between nodes exists, it needs to be sorted to end }
+
+function shouldSortToEnd(node) {
+  const attr = attributeMap.get(node);
+  return !!attr && !!attr.hasComment;
+}
+
+function contextCompare(a, b, options) {
+  let aProp = propName(a);
+  let bProp = propName(b);
+
+  const aSortToEnd = shouldSortToEnd(a);
+  const bSortToEnd = shouldSortToEnd(b);
+  if (aSortToEnd && !bSortToEnd) {
+    return 1;
+  }
+  if (!aSortToEnd && bSortToEnd) {
+    return -1;
+  }
+
+  if (options.reservedFirst) {
+    const aIsReserved = isReservedPropName(aProp, options.reservedList);
+    const bIsReserved = isReservedPropName(bProp, options.reservedList);
+    if (aIsReserved && !bIsReserved) {
+      return -1;
+    }
+    if (!aIsReserved && bIsReserved) {
+      return 1;
+    }
+  }
+
+  if (options.callbacksLast) {
+    const aIsCallback = propTypesSortUtil.isCallbackPropName(aProp);
+    const bIsCallback = propTypesSortUtil.isCallbackPropName(bProp);
+    if (aIsCallback && !bIsCallback) {
+      return 1;
+    }
+    if (!aIsCallback && bIsCallback) {
+      return -1;
+    }
+  }
+
+  if (options.shorthandFirst || options.shorthandLast) {
+    const shorthandSign = options.shorthandFirst ? -1 : 1;
+    if (!a.value && b.value) {
+      return shorthandSign;
+    }
+    if (a.value && !b.value) {
+      return -shorthandSign;
+    }
+  }
+
+  if (options.multiline !== 'ignore') {
+    const multilineSign = options.multiline === 'first' ? -1 : 1;
+    const aIsMultiline = isMultilineProp(a);
+    const bIsMultiline = isMultilineProp(b);
+    if (aIsMultiline && !bIsMultiline) {
+      return multilineSign;
+    }
+    if (!aIsMultiline && bIsMultiline) {
+      return -multilineSign;
+    }
+  }
+
+  if (options.noSortAlphabetically) {
+    return 0;
+  }
+
+  const actualLocale = options.locale === 'auto' ? undefined : options.locale;
+
+  if (options.ignoreCase) {
+    aProp = aProp.toLowerCase();
+    bProp = bProp.toLowerCase();
+    return aProp.localeCompare(bProp, actualLocale);
+  }
+  if (aProp === bProp) {
+    return 0;
+  }
+  if (options.locale === 'auto') {
+    return aProp < bProp ? -1 : 1;
+  }
+  return aProp.localeCompare(bProp, actualLocale);
+}
+
+/**
+ * Create an array of arrays where each subarray is composed of attributes
+ * that are considered sortable.
+ * @param {Array<JSXSpreadAttribute|JSXAttribute>} attributes
+ * @param {Object} context The context of the rule
+ * @return {Array<Array<JSXAttribute>>}
+ */
+function getGroupsOfSortableAttributes(attributes, context) {
+  const sourceCode = getSourceCode(context);
+
+  const sortableAttributeGroups = [];
+  let groupCount = 0;
+  function addtoSortableAttributeGroups(attribute) {
+    sortableAttributeGroups[groupCount - 1].push(attribute);
+  }
+
+  for (let i = 0; i < attributes.length; i++) {
+    const attribute = attributes[i];
+    const nextAttribute = attributes[i + 1];
+    const attributeline = attribute.loc.start.line;
+    let comment = [];
+    try {
+      comment = sourceCode.getCommentsAfter(attribute);
+    } catch (e) { /**/ }
+    const lastAttr = attributes[i - 1];
+    const attrIsSpread = attribute.type === 'JSXSpreadAttribute';
+
+    // If we have no groups or if the last attribute was JSXSpreadAttribute
+    // then we start a new group. Append attributes to the group until we
+    // come across another JSXSpreadAttribute or exhaust the array.
+    if (
+      !lastAttr
+      || (lastAttr.type === 'JSXSpreadAttribute' && !attrIsSpread)
+    ) {
+      groupCount += 1;
+      sortableAttributeGroups[groupCount - 1] = [];
+    }
+    if (!attrIsSpread) {
+      if (comment.length === 0) {
+        attributeMap.set(attribute, { end: attribute.range[1], hasComment: false });
+        addtoSortableAttributeGroups(attribute);
+      } else {
+        const firstComment = comment[0];
+        const commentline = firstComment.loc.start.line;
+        if (comment.length === 1) {
+          if (attributeline + 1 === commentline && nextAttribute) {
+            attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true });
+            addtoSortableAttributeGroups(attribute);
+            i += 1;
+          } else if (attributeline === commentline) {
+            if (firstComment.type === 'Block' && nextAttribute) {
+              attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true });
+              i += 1;
+            } else if (firstComment.type === 'Block') {
+              attributeMap.set(attribute, { end: firstComment.range[1], hasComment: true });
+            } else {
+              attributeMap.set(attribute, { end: firstComment.range[1], hasComment: false });
+            }
+            addtoSortableAttributeGroups(attribute);
+          }
+        } else if (comment.length > 1 && attributeline + 1 === comment[1].loc.start.line && nextAttribute) {
+          const commentNextAttribute = sourceCode.getCommentsAfter(nextAttribute);
+          attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true });
+          if (
+            commentNextAttribute.length === 1
+            && nextAttribute.loc.start.line === commentNextAttribute[0].loc.start.line
+          ) {
+            attributeMap.set(attribute, { end: commentNextAttribute[0].range[1], hasComment: true });
+          }
+          addtoSortableAttributeGroups(attribute);
+          i += 1;
+        }
+      }
+    }
+  }
+  return sortableAttributeGroups;
+}
+
+function generateFixerFunction(node, context, reservedList) {
+  const attributes = node.attributes.slice(0);
+  const configuration = context.options[0] || {};
+  const ignoreCase = configuration.ignoreCase || false;
+  const callbacksLast = configuration.callbacksLast || false;
+  const shorthandFirst = configuration.shorthandFirst || false;
+  const shorthandLast = configuration.shorthandLast || false;
+  const multiline = configuration.multiline || 'ignore';
+  const noSortAlphabetically = configuration.noSortAlphabetically || false;
+  const reservedFirst = configuration.reservedFirst || false;
+  const locale = configuration.locale || 'auto';
+
+  // Sort props according to the context. Only supports ignoreCase.
+  // Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
+  // we only consider groups of sortable attributes.
+  const options = {
+    ignoreCase,
+    callbacksLast,
+    shorthandFirst,
+    shorthandLast,
+    multiline,
+    noSortAlphabetically,
+    reservedFirst,
+    reservedList,
+    locale,
+  };
+  const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes, context);
+  const sortedAttributeGroups = sortableAttributeGroups
+    .slice(0)
+    .map((group) => toSorted(group, (a, b) => contextCompare(a, b, options)));
+
+  return function fixFunction(fixer) {
+    const fixers = [];
+    let source = getText(context);
+
+    sortableAttributeGroups.forEach((sortableGroup, ii) => {
+      sortableGroup.forEach((attr, jj) => {
+        const sortedAttr = sortedAttributeGroups[ii][jj];
+        const sortedAttrText = source.slice(sortedAttr.range[0], attributeMap.get(sortedAttr).end);
+        fixers.push({
+          range: [attr.range[0], attributeMap.get(attr).end],
+          text: sortedAttrText,
+        });
+      });
+    });
+
+    fixers.sort((a, b) => b.range[0] - a.range[0]);
+
+    const firstFixer = fixers[0];
+    const lastFixer = fixers[fixers.length - 1];
+    const rangeStart = lastFixer ? lastFixer.range[0] : 0;
+    const rangeEnd = firstFixer ? firstFixer.range[1] : -0;
+
+    fixers.forEach((fix) => {
+      source = `${source.slice(0, fix.range[0])}${fix.text}${source.slice(fix.range[1])}`;
+    });
+
+    return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
+  };
+}
+
+/**
+ * Checks if the `reservedFirst` option is valid
+ * @param {Object} context The context of the rule
+ * @param {boolean | string[]} reservedFirst The `reservedFirst` option
+ * @return {Function | undefined} If an error is detected, a function to generate the error message, otherwise, `undefined`
+ */
+// eslint-disable-next-line consistent-return
+function validateReservedFirstConfig(context, reservedFirst) {
+  if (reservedFirst) {
+    if (Array.isArray(reservedFirst)) {
+      // Only allow a subset of reserved words in customized lists
+      const nonReservedWords = reservedFirst.filter((word) => !isReservedPropName(
+        word,
+        RESERVED_PROPS_LIST
+      ));
+
+      if (reservedFirst.length === 0) {
+        return function Report(decl) {
+          report(context, messages.listIsEmpty, 'listIsEmpty', {
+            node: decl,
+          });
+        };
+      }
+      if (nonReservedWords.length > 0) {
+        return function Report(decl) {
+          report(context, messages.noUnreservedProps, 'noUnreservedProps', {
+            node: decl,
+            data: {
+              unreservedWords: nonReservedWords.toString(),
+            },
+          });
+        };
+      }
+    }
+  }
+}
+
+const reportedNodeAttributes = new WeakMap();
+/**
+ * Check if the current node attribute has already been reported with the same error type
+ * if that's the case then we don't report a new error
+ * otherwise we report the error
+ * @param {Object} nodeAttribute The node attribute to be reported
+ * @param {string} errorType The error type to be reported
+ * @param {Object} node The parent node for the node attribute
+ * @param {Object} context The context of the rule
+ * @param {Array<String>} reservedList The list of reserved props
+ */
+function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedList) {
+  const errors = reportedNodeAttributes.get(nodeAttribute) || [];
+
+  if (includes(errors, errorType)) {
+    return;
+  }
+
+  errors.push(errorType);
+
+  reportedNodeAttributes.set(nodeAttribute, errors);
+
+  report(context, messages[errorType], errorType, {
+    node: nodeAttribute.name,
+    fix: generateFixerFunction(node, context, reservedList),
+  });
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce props alphabetical sorting',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-sort-props'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        // Whether callbacks (prefixed with "on") should be listed at the very end,
+        // after all other props. Supersedes shorthandLast.
+        callbacksLast: {
+          type: 'boolean',
+        },
+        // Whether shorthand properties (without a value) should be listed first
+        shorthandFirst: {
+          type: 'boolean',
+        },
+        // Whether shorthand properties (without a value) should be listed last
+        shorthandLast: {
+          type: 'boolean',
+        },
+        // Whether multiline properties should be listed first or last
+        multiline: {
+          enum: ['ignore', 'first', 'last'],
+          default: 'ignore',
+        },
+        ignoreCase: {
+          type: 'boolean',
+        },
+        // Whether alphabetical sorting should be enforced
+        noSortAlphabetically: {
+          type: 'boolean',
+        },
+        reservedFirst: {
+          type: ['array', 'boolean'],
+        },
+        locale: {
+          type: 'string',
+          default: 'auto',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const ignoreCase = configuration.ignoreCase || false;
+    const callbacksLast = configuration.callbacksLast || false;
+    const shorthandFirst = configuration.shorthandFirst || false;
+    const shorthandLast = configuration.shorthandLast || false;
+    const multiline = configuration.multiline || 'ignore';
+    const noSortAlphabetically = configuration.noSortAlphabetically || false;
+    const reservedFirst = configuration.reservedFirst || false;
+    const reservedFirstError = validateReservedFirstConfig(context, reservedFirst);
+    const reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST;
+    const locale = configuration.locale || 'auto';
+
+    return {
+      Program() {
+        attributeMap = new WeakMap();
+      },
+
+      JSXOpeningElement(node) {
+        // `dangerouslySetInnerHTML` is only "reserved" on DOM components
+        const nodeReservedList = reservedFirst && !jsxUtil.isDOMComponent(node) ? reservedList.filter((prop) => prop !== 'dangerouslySetInnerHTML') : reservedList;
+
+        node.attributes.reduce((memo, decl, idx, attrs) => {
+          if (decl.type === 'JSXSpreadAttribute') {
+            return attrs[idx + 1];
+          }
+
+          let previousPropName = propName(memo);
+          let currentPropName = propName(decl);
+          const previousValue = memo.value;
+          const currentValue = decl.value;
+          const previousIsCallback = propTypesSortUtil.isCallbackPropName(previousPropName);
+          const currentIsCallback = propTypesSortUtil.isCallbackPropName(currentPropName);
+
+          if (ignoreCase) {
+            previousPropName = previousPropName.toLowerCase();
+            currentPropName = currentPropName.toLowerCase();
+          }
+
+          if (reservedFirst) {
+            if (reservedFirstError) {
+              reservedFirstError(decl);
+              return memo;
+            }
+
+            const previousIsReserved = isReservedPropName(previousPropName, nodeReservedList);
+            const currentIsReserved = isReservedPropName(currentPropName, nodeReservedList);
+
+            if (previousIsReserved && !currentIsReserved) {
+              return decl;
+            }
+            if (!previousIsReserved && currentIsReserved) {
+              reportNodeAttribute(decl, 'listReservedPropsFirst', node, context, nodeReservedList);
+
+              return memo;
+            }
+          }
+
+          if (callbacksLast) {
+            if (!previousIsCallback && currentIsCallback) {
+              // Entering the callback prop section
+              return decl;
+            }
+            if (previousIsCallback && !currentIsCallback) {
+              // Encountered a non-callback prop after a callback prop
+              reportNodeAttribute(memo, 'listCallbacksLast', node, context, nodeReservedList);
+
+              return memo;
+            }
+          }
+
+          if (shorthandFirst) {
+            if (currentValue && !previousValue) {
+              return decl;
+            }
+            if (!currentValue && previousValue) {
+              reportNodeAttribute(decl, 'listShorthandFirst', node, context, nodeReservedList);
+
+              return memo;
+            }
+          }
+
+          if (shorthandLast) {
+            if (!currentValue && previousValue) {
+              return decl;
+            }
+            if (currentValue && !previousValue) {
+              reportNodeAttribute(memo, 'listShorthandLast', node, context, nodeReservedList);
+
+              return memo;
+            }
+          }
+
+          const previousIsMultiline = isMultilineProp(memo);
+          const currentIsMultiline = isMultilineProp(decl);
+          if (multiline === 'first') {
+            if (previousIsMultiline && !currentIsMultiline) {
+              // Exiting the multiline prop section
+              return decl;
+            }
+            if (!previousIsMultiline && currentIsMultiline) {
+              // Encountered a non-multiline prop before a multiline prop
+              reportNodeAttribute(decl, 'listMultilineFirst', node, context, nodeReservedList);
+
+              return memo;
+            }
+          } else if (multiline === 'last') {
+            if (!previousIsMultiline && currentIsMultiline) {
+              // Entering the multiline prop section
+              return decl;
+            }
+            if (previousIsMultiline && !currentIsMultiline) {
+              // Encountered a non-multiline prop after a multiline prop
+              reportNodeAttribute(memo, 'listMultilineLast', node, context, nodeReservedList);
+
+              return memo;
+            }
+          }
+
+          if (
+            !noSortAlphabetically
+            && (
+              (ignoreCase || locale !== 'auto')
+                ? previousPropName.localeCompare(currentPropName, locale === 'auto' ? undefined : locale) > 0
+                : previousPropName > currentPropName
+            )
+          ) {
+            reportNodeAttribute(decl, 'sortPropsByAlpha', node, context, nodeReservedList);
+
+            return memo;
+          }
+
+          return decl;
+        }, node.attributes[0]);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-space-before-closing.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-space-before-closing.d.ts","sourceRoot":"","sources":["jsx-space-before-closing.js"],"names":[],"mappings":"wBAyBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-space-before-closing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+/**
+ * @fileoverview Validate spacing before closing bracket in JSX.
+ * @author ryym
+ * @deprecated
+ */
+
+'use strict';
+
+const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket');
+const docsUrl = require('../util/docsUrl');
+const log = require('../util/log');
+const report = require('../util/report');
+const getSourceCode = require('../util/eslint').getSourceCode;
+
+let isWarnedForDeprecation = false;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noSpaceBeforeClose: 'A space is forbidden before closing bracket',
+  needSpaceBeforeClose: 'A space is required before closing bracket',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    deprecated: true,
+    replacedBy: ['jsx-tag-spacing'],
+    docs: {
+      description: 'Enforce spacing before closing bracket in JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-space-before-closing'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      enum: ['always', 'never'],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || 'always';
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXOpeningElement(node) {
+        if (!node.selfClosing) {
+          return;
+        }
+
+        const sourceCode = getSourceCode(context);
+
+        const leftToken = getTokenBeforeClosingBracket(node);
+        const closingSlash = /** @type {import('eslint').AST.Token} */ (sourceCode.getTokenAfter(leftToken));
+
+        if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
+          return;
+        }
+
+        if (configuration === 'always' && !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
+          report(context, messages.needSpaceBeforeClose, 'needSpaceBeforeClose', {
+            loc: closingSlash.loc.start,
+            fix(fixer) {
+              return fixer.insertTextBefore(closingSlash, ' ');
+            },
+          });
+        } else if (configuration === 'never' && sourceCode.isSpaceBetweenTokens(leftToken, closingSlash)) {
+          report(context, messages.noSpaceBeforeClose, 'noSpaceBeforeClose', {
+            loc: closingSlash.loc.start,
+            fix(fixer) {
+              const previousToken = sourceCode.getTokenBefore(closingSlash);
+              return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
+            },
+          });
+        }
+      },
+
+      Program() {
+        if (isWarnedForDeprecation) {
+          return;
+        }
+
+        log('The react/jsx-space-before-closing rule is deprecated. '
+            + 'Please use the react/jsx-tag-spacing rule with the '
+            + '"beforeSelfClosing" option instead.');
+        isWarnedForDeprecation = true;
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-tag-spacing.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-tag-spacing.d.ts","sourceRoot":"","sources":["jsx-tag-spacing.js"],"names":[],"mappings":"wBAqQW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-tag-spacing.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,328 @@
+/**
+ * @fileoverview Validates whitespace in and around the JSX opening and closing brackets
+ * @author Diogo Franco (Kovensky)
+ */
+
+'use strict';
+
+const getTokenBeforeClosingBracket = require('../util/getTokenBeforeClosingBracket');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getFirstTokens = eslintUtil.getFirstTokens;
+const getSourceCode = eslintUtil.getSourceCode;
+
+const messages = {
+  selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`',
+  selfCloseSlashNeedSpace: 'Whitespace is required between `/` and `>`; write `/ >`',
+  closeSlashNoSpace: 'Whitespace is forbidden between `<` and `/`; write `</`',
+  closeSlashNeedSpace: 'Whitespace is required between `<` and `/`; write `< /`',
+  beforeSelfCloseNoSpace: 'A space is forbidden before closing bracket',
+  beforeSelfCloseNeedSpace: 'A space is required before closing bracket',
+  beforeSelfCloseNeedNewline: 'A newline is required before closing bracket',
+  afterOpenNoSpace: 'A space is forbidden after opening bracket',
+  afterOpenNeedSpace: 'A space is required after opening bracket',
+  beforeCloseNoSpace: 'A space is forbidden before closing bracket',
+  beforeCloseNeedSpace: 'Whitespace is required before closing bracket',
+  beforeCloseNeedNewline: 'A newline is required before closing bracket',
+};
+
+// ------------------------------------------------------------------------------
+// Validators
+// ------------------------------------------------------------------------------
+
+function validateClosingSlash(context, node, option) {
+  const sourceCode = getSourceCode(context);
+
+  let adjacent;
+
+  if (node.selfClosing) {
+    const lastTokens = sourceCode.getLastTokens(node, 2);
+
+    adjacent = !sourceCode.isSpaceBetweenTokens(lastTokens[0], lastTokens[1]);
+
+    if (option === 'never') {
+      if (!adjacent) {
+        report(context, messages.selfCloseSlashNoSpace, 'selfCloseSlashNoSpace', {
+          node,
+          loc: {
+            start: lastTokens[0].loc.start,
+            end: lastTokens[1].loc.end,
+          },
+          fix(fixer) {
+            return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]]);
+          },
+        });
+      }
+    } else if (option === 'always' && adjacent) {
+      report(context, messages.selfCloseSlashNeedSpace, 'selfCloseSlashNeedSpace', {
+        node,
+        loc: {
+          start: lastTokens[0].loc.start,
+          end: lastTokens[1].loc.end,
+        },
+        fix(fixer) {
+          return fixer.insertTextBefore(lastTokens[1], ' ');
+        },
+      });
+    }
+  } else {
+    const firstTokens = getFirstTokens(context, node, 2);
+
+    adjacent = !sourceCode.isSpaceBetweenTokens(firstTokens[0], firstTokens[1]);
+
+    if (option === 'never') {
+      if (!adjacent) {
+        report(context, messages.closeSlashNoSpace, 'closeSlashNoSpace', {
+          node,
+          loc: {
+            start: firstTokens[0].loc.start,
+            end: firstTokens[1].loc.end,
+          },
+          fix(fixer) {
+            return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]]);
+          },
+        });
+      }
+    } else if (option === 'always' && adjacent) {
+      report(context, messages.closeSlashNeedSpace, 'closeSlashNeedSpace', {
+        node,
+        loc: {
+          start: firstTokens[0].loc.start,
+          end: firstTokens[1].loc.end,
+        },
+        fix(fixer) {
+          return fixer.insertTextBefore(firstTokens[1], ' ');
+        },
+      });
+    }
+  }
+}
+
+function validateBeforeSelfClosing(context, node, option) {
+  const sourceCode = getSourceCode(context);
+  const leftToken = getTokenBeforeClosingBracket(node);
+  const closingSlash = sourceCode.getTokenAfter(leftToken);
+
+  if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
+    if (leftToken.loc.end.line === closingSlash.loc.start.line) {
+      report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
+        node,
+        loc: leftToken.loc.end,
+        fix(fixer) {
+          return fixer.insertTextBefore(closingSlash, '\n');
+        },
+      });
+      return;
+    }
+  }
+
+  if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
+    return;
+  }
+
+  const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash);
+
+  if ((option === 'always' || option === 'proportional-always') && adjacent) {
+    report(context, messages.beforeSelfCloseNeedSpace, 'beforeSelfCloseNeedSpace', {
+      node,
+      loc: closingSlash.loc.start,
+      fix(fixer) {
+        return fixer.insertTextBefore(closingSlash, ' ');
+      },
+    });
+  } else if (option === 'never' && !adjacent) {
+    report(context, messages.beforeSelfCloseNoSpace, 'beforeSelfCloseNoSpace', {
+      node,
+      loc: closingSlash.loc.start,
+      fix(fixer) {
+        const previousToken = sourceCode.getTokenBefore(closingSlash);
+        return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
+      },
+    });
+  }
+}
+
+function validateAfterOpening(context, node, option) {
+  const sourceCode = getSourceCode(context);
+  const openingToken = sourceCode.getTokenBefore(node.name);
+
+  if (option === 'allow-multiline') {
+    if (openingToken.loc.start.line !== node.name.loc.start.line) {
+      return;
+    }
+  }
+
+  const adjacent = !sourceCode.isSpaceBetweenTokens(openingToken, node.name);
+
+  if (option === 'never' || option === 'allow-multiline') {
+    if (!adjacent) {
+      report(context, messages.afterOpenNoSpace, 'afterOpenNoSpace', {
+        node,
+        loc: {
+          start: openingToken.loc.start,
+          end: node.name.loc.start,
+        },
+        fix(fixer) {
+          return fixer.removeRange([openingToken.range[1], node.name.range[0]]);
+        },
+      });
+    }
+  } else if (option === 'always' && adjacent) {
+    report(context, messages.afterOpenNeedSpace, 'afterOpenNeedSpace', {
+      node,
+      loc: {
+        start: openingToken.loc.start,
+        end: node.name.loc.start,
+      },
+      fix(fixer) {
+        return fixer.insertTextBefore(node.name, ' ');
+      },
+    });
+  }
+}
+
+function validateBeforeClosing(context, node, option) {
+  // Don't enforce this rule for self closing tags
+  if (!node.selfClosing) {
+    const sourceCode = getSourceCode(context);
+    const leftToken = option === 'proportional-always'
+      ? getTokenBeforeClosingBracket(node)
+      : sourceCode.getLastTokens(node, 2)[0];
+    const closingToken = sourceCode.getTokenAfter(leftToken);
+
+    if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
+      if (leftToken.loc.end.line === closingToken.loc.start.line) {
+        report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
+          node,
+          loc: leftToken.loc.end,
+          fix(fixer) {
+            return fixer.insertTextBefore(closingToken, '\n');
+          },
+        });
+        return;
+      }
+    }
+
+    if (leftToken.loc.start.line !== closingToken.loc.start.line) {
+      return;
+    }
+
+    const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingToken);
+
+    if (option === 'never' && !adjacent) {
+      report(context, messages.beforeCloseNoSpace, 'beforeCloseNoSpace', {
+        node,
+        loc: {
+          start: leftToken.loc.end,
+          end: closingToken.loc.start,
+        },
+        fix(fixer) {
+          return fixer.removeRange([leftToken.range[1], closingToken.range[0]]);
+        },
+      });
+    } else if (option === 'always' && adjacent) {
+      report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
+        node,
+        loc: {
+          start: leftToken.loc.end,
+          end: closingToken.loc.start,
+        },
+        fix(fixer) {
+          return fixer.insertTextBefore(closingToken, ' ');
+        },
+      });
+    } else if (option === 'proportional-always' && node.type === 'JSXOpeningElement' && adjacent !== (node.loc.start.line === node.loc.end.line)) {
+      report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
+        node,
+        loc: {
+          start: leftToken.loc.end,
+          end: closingToken.loc.start,
+        },
+        fix(fixer) {
+          return fixer.insertTextBefore(closingToken, ' ');
+        },
+      });
+    }
+  }
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const optionDefaults = {
+  closingSlash: 'never',
+  beforeSelfClosing: 'always',
+  afterOpening: 'never',
+  beforeClosing: 'allow',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce whitespace in and around the JSX opening and closing brackets',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-tag-spacing'),
+    },
+    fixable: 'whitespace',
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          closingSlash: {
+            enum: ['always', 'never', 'allow'],
+          },
+          beforeSelfClosing: {
+            enum: ['always', 'proportional-always', 'never', 'allow'],
+          },
+          afterOpening: {
+            enum: ['always', 'allow-multiline', 'never', 'allow'],
+          },
+          beforeClosing: {
+            enum: ['always', 'proportional-always', 'never', 'allow'],
+          },
+        },
+        default: optionDefaults,
+        additionalProperties: false,
+      },
+    ],
+  },
+  create(context) {
+    const options = Object.assign({}, optionDefaults, context.options[0]);
+
+    return {
+      JSXOpeningElement(node) {
+        if (options.closingSlash !== 'allow' && node.selfClosing) {
+          validateClosingSlash(context, node, options.closingSlash);
+        }
+        if (options.afterOpening !== 'allow') {
+          validateAfterOpening(context, node, options.afterOpening);
+        }
+        if (options.beforeSelfClosing !== 'allow' && node.selfClosing) {
+          validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
+        }
+        if (options.beforeClosing !== 'allow') {
+          validateBeforeClosing(context, node, options.beforeClosing);
+        }
+      },
+      JSXClosingElement(node) {
+        if (options.afterOpening !== 'allow') {
+          validateAfterOpening(context, node, options.afterOpening);
+        }
+        if (options.closingSlash !== 'allow') {
+          validateClosingSlash(context, node, options.closingSlash);
+        }
+        if (options.beforeClosing !== 'allow') {
+          validateBeforeClosing(context, node, options.beforeClosing);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-uses-react.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-uses-react.d.ts","sourceRoot":"","sources":["jsx-uses-react.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-react.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Prevent React to be marked as unused
+ * @author Glen Mailer
+ */
+
+'use strict';
+
+const pragmaUtil = require('../util/pragma');
+const docsUrl = require('../util/docsUrl');
+const markVariableAsUsed = require('../util/eslint').markVariableAsUsed;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  // eslint-disable-next-line eslint-plugin/prefer-message-ids -- https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/292
+  meta: {
+    docs: {
+      description: 'Disallow React to be incorrectly marked as unused',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('jsx-uses-react'),
+    },
+    schema: [],
+  },
+
+  create(context) {
+    const pragma = pragmaUtil.getFromContext(context);
+    const fragment = pragmaUtil.getFragmentFromContext(context);
+
+    /**
+     * @param {ASTNode} node
+     * @returns {void}
+     */
+    function handleOpeningElement(node) {
+      markVariableAsUsed(pragma, node, context);
+    }
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      JSXOpeningElement: handleOpeningElement,
+      JSXOpeningFragment: handleOpeningElement,
+      JSXFragment(node) {
+        markVariableAsUsed(fragment, node, context);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-uses-vars.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-uses-vars.d.ts","sourceRoot":"","sources":["jsx-uses-vars.js"],"names":[],"mappings":"wBAiBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-uses-vars.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Prevent variables used in JSX to be marked as unused
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const markVariableAsUsed = require('../util/eslint').markVariableAsUsed;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const isTagNameRe = /^[a-z]/;
+const isTagName = (name) => isTagNameRe.test(name);
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  // eslint-disable-next-line eslint-plugin/prefer-message-ids -- https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/292
+  meta: {
+    docs: {
+      description: 'Disallow variables used in JSX to be incorrectly marked as unused',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('jsx-uses-vars'),
+    },
+    schema: [],
+  },
+
+  create(context) {
+    return {
+      JSXOpeningElement(node) {
+        let name;
+        if (node.name.namespace) {
+          // <Foo:Bar>
+          return;
+        }
+        if (node.name.name) {
+          // <Foo>
+          name = node.name.name;
+          // Exclude lowercase tag names like <div>
+          if (isTagName(name)) {
+            return;
+          }
+        } else if (node.name.object) {
+          // <Foo...Bar>
+          let parent = node.name.object;
+          while (parent.object) {
+            parent = parent.object;
+          }
+          name = parent.name;
+        } else {
+          return;
+        }
+
+        markVariableAsUsed(name, node, context);
+      },
+
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=jsx-wrap-multilines.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx-wrap-multilines.d.ts","sourceRoot":"","sources":["jsx-wrap-multilines.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,275 @@
+/**
+ * @fileoverview Prevent missing parentheses around multilines JSX
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const has = require('hasown');
+const docsUrl = require('../util/docsUrl');
+const eslintUtil = require('../util/eslint');
+const jsxUtil = require('../util/jsx');
+const reportC = require('../util/report');
+const isParenthesized = require('../util/ast').isParenthesized;
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = {
+  declaration: 'parens',
+  assignment: 'parens',
+  return: 'parens',
+  arrow: 'parens',
+  condition: 'ignore',
+  logical: 'ignore',
+  prop: 'ignore',
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  missingParens: 'Missing parentheses around multilines JSX',
+  extraParens: 'Expected no parentheses around multilines JSX',
+  parensOnNewLines: 'Parentheses around JSX should be on separate lines',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow missing parentheses around multiline JSX',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('jsx-wrap-multilines'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      // true/false are for backwards compatibility
+      properties: {
+        declaration: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        assignment: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        return: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        arrow: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        condition: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        logical: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+        prop: {
+          enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    function getOption(type) {
+      const userOptions = context.options[0] || {};
+      if (has(userOptions, type)) {
+        return userOptions[type];
+      }
+      return DEFAULTS[type];
+    }
+
+    function isEnabled(type) {
+      const option = getOption(type);
+      return option && option !== 'ignore';
+    }
+
+    function needsOpeningNewLine(node) {
+      const previousToken = getSourceCode(context).getTokenBefore(node);
+
+      if (!isParenthesized(context, node)) {
+        return false;
+      }
+
+      if (previousToken.loc.end.line === node.loc.start.line) {
+        return true;
+      }
+
+      return false;
+    }
+
+    function needsClosingNewLine(node) {
+      const nextToken = getSourceCode(context).getTokenAfter(node);
+
+      if (!isParenthesized(context, node)) {
+        return false;
+      }
+
+      if (node.loc.end.line === nextToken.loc.end.line) {
+        return true;
+      }
+
+      return false;
+    }
+
+    function isMultilines(node) {
+      return node.loc.start.line !== node.loc.end.line;
+    }
+
+    function report(node, messageId, fix) {
+      reportC(context, messages[messageId], messageId, {
+        node,
+        fix,
+      });
+    }
+
+    function trimTokenBeforeNewline(node, tokenBefore) {
+      // if the token before the jsx is a bracket or curly brace
+      // we don't want a space between the opening parentheses and the multiline jsx
+      const isBracket = tokenBefore.value === '{' || tokenBefore.value === '[';
+      return `${tokenBefore.value.trim()}${isBracket ? '' : ' '}`;
+    }
+
+    function check(node, type) {
+      if (!node || !jsxUtil.isJSX(node)) {
+        return;
+      }
+
+      const sourceCode = getSourceCode(context);
+      const option = getOption(type);
+
+      if ((option === true || option === 'parens') && !isParenthesized(context, node) && isMultilines(node)) {
+        report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(${getText(context, node)})`));
+      }
+
+      if (option === 'parens-new-line' && isMultilines(node)) {
+        if (!isParenthesized(context, node)) {
+          const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
+          const tokenAfter = sourceCode.getTokenAfter(node, { includeComments: true });
+          const start = node.loc.start;
+          if (tokenBefore.loc.end.line < start.line) {
+            // Strip newline after operator if parens newline is specified
+            report(
+              node,
+              'missingParens',
+              (fixer) => fixer.replaceTextRange(
+                [tokenBefore.range[0], tokenAfter && (tokenAfter.value === ';' || tokenAfter.value === '}') ? tokenAfter.range[0] : node.range[1]],
+                `${trimTokenBeforeNewline(node, tokenBefore)}(\n${start.column > 0 ? ' '.repeat(start.column) : ''}${getText(context, node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})`
+              )
+            );
+          } else {
+            report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(\n${getText(context, node)}\n)`));
+          }
+        } else {
+          const needsOpening = needsOpeningNewLine(node);
+          const needsClosing = needsClosingNewLine(node);
+          if (needsOpening || needsClosing) {
+            report(node, 'parensOnNewLines', (fixer) => {
+              const text = getText(context, node);
+              let fixed = text;
+              if (needsOpening) {
+                fixed = `\n${fixed}`;
+              }
+              if (needsClosing) {
+                fixed = `${fixed}\n`;
+              }
+              return fixer.replaceText(node, fixed);
+            });
+          }
+        }
+      }
+
+      if (option === 'never' && isParenthesized(context, node)) {
+        const tokenBefore = sourceCode.getTokenBefore(node);
+        const tokenAfter = sourceCode.getTokenAfter(node);
+        report(node, 'extraParens', (fixer) => fixer.replaceTextRange(
+          [tokenBefore.range[0], tokenAfter.range[1]],
+          getText(context, node)
+        ));
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+
+      VariableDeclarator(node) {
+        const type = 'declaration';
+        if (!isEnabled(type)) {
+          return;
+        }
+        if (!isEnabled('condition') && node.init && node.init.type === 'ConditionalExpression') {
+          check(node.init.consequent, type);
+          check(node.init.alternate, type);
+          return;
+        }
+        check(node.init, type);
+      },
+
+      AssignmentExpression(node) {
+        const type = 'assignment';
+        if (!isEnabled(type)) {
+          return;
+        }
+        if (!isEnabled('condition') && node.right.type === 'ConditionalExpression') {
+          check(node.right.consequent, type);
+          check(node.right.alternate, type);
+          return;
+        }
+        check(node.right, type);
+      },
+
+      ReturnStatement(node) {
+        const type = 'return';
+        if (isEnabled(type)) {
+          check(node.argument, type);
+        }
+      },
+
+      'ArrowFunctionExpression:exit': (node) => {
+        const arrowBody = node.body;
+        const type = 'arrow';
+
+        if (isEnabled(type) && arrowBody.type !== 'BlockStatement') {
+          check(arrowBody, type);
+        }
+      },
+
+      ConditionalExpression(node) {
+        const type = 'condition';
+        if (isEnabled(type)) {
+          check(node.consequent, type);
+          check(node.alternate, type);
+        }
+      },
+
+      LogicalExpression(node) {
+        const type = 'logical';
+        if (isEnabled(type)) {
+          check(node.right, type);
+        }
+      },
+
+      JSXAttribute(node) {
+        const type = 'prop';
+        if (isEnabled(type) && node.value && node.value.type === 'JSXExpressionContainer') {
+          check(node.value.expression, type);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-access-state-in-setstate.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-access-state-in-setstate.d.ts","sourceRoot":"","sources":["no-access-state-in-setstate.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-access-state-in-setstate.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,211 @@
+/**
+ * @fileoverview Prevent usage of this.state within setState
+ * @author Rolf Erik Lekang, Jørgen Aaberg
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const report = require('../util/report');
+const getScope = require('../util/eslint').getScope;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  useCallback: 'Use callback in setState when referencing the previous state.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow when this.state is accessed within setState',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-access-state-in-setstate'),
+    },
+
+    messages,
+  },
+
+  create(context) {
+    function isSetStateCall(node) {
+      return astUtil.isCallExpression(node)
+        && node.callee.property
+        && node.callee.property.name === 'setState'
+        && node.callee.object.type === 'ThisExpression';
+    }
+
+    function isFirstArgumentInSetStateCall(current, node) {
+      if (!isSetStateCall(current)) {
+        return false;
+      }
+      while (node && node.parent !== current) {
+        node = node.parent;
+      }
+      return current.arguments[0] === node;
+    }
+
+    /**
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    function isClassComponent(node) {
+      return !!(
+        componentUtil.getParentES6Component(context, node)
+        || componentUtil.getParentES5Component(context, node)
+      );
+    }
+
+    // The methods array contains all methods or functions that are using this.state
+    // or that are calling another method or function using this.state
+    const methods = [];
+    // The vars array contains all variables that contains this.state
+    const vars = [];
+    return {
+      CallExpression(node) {
+        if (!isClassComponent(node)) {
+          return;
+        }
+        // Appends all the methods that are calling another
+        // method containing this.state to the methods array
+        methods.forEach((method) => {
+          if ('name' in node.callee && node.callee.name === method.methodName) {
+            let current = node.parent;
+            while (current.type !== 'Program') {
+              if (current.type === 'MethodDefinition') {
+                methods.push({
+                  methodName: 'name' in current.key ? current.key.name : undefined,
+                  node: method.node,
+                });
+                break;
+              }
+              current = current.parent;
+            }
+          }
+        });
+
+        // Finding all CallExpressions that is inside a setState
+        // to further check if they contains this.state
+        let current = node.parent;
+        while (current.type !== 'Program') {
+          if (isFirstArgumentInSetStateCall(current, node)) {
+            const methodName = 'name' in node.callee ? node.callee.name : undefined;
+            methods.forEach((method) => {
+              if (method.methodName === methodName) {
+                report(context, messages.useCallback, 'useCallback', {
+                  node: method.node,
+                });
+              }
+            });
+
+            break;
+          }
+          current = current.parent;
+        }
+      },
+
+      MemberExpression(node) {
+        if (
+          'name' in node.property
+          && node.property.name === 'state'
+          && node.object.type === 'ThisExpression'
+          && isClassComponent(node)
+        ) {
+          /** @type {import('eslint').Rule.Node} */
+          let current = node;
+          while (current.type !== 'Program') {
+            // Reporting if this.state is directly within this.setState
+            if (isFirstArgumentInSetStateCall(current, node)) {
+              report(context, messages.useCallback, 'useCallback', {
+                node,
+              });
+              break;
+            }
+
+            // Storing all functions and methods that contains this.state
+            if (current.type === 'MethodDefinition') {
+              methods.push({
+                methodName: 'name' in current.key ? current.key.name : undefined,
+                node,
+              });
+              break;
+            } else if (
+              current.type === 'FunctionExpression'
+              && 'key' in current.parent
+              && current.parent.key
+            ) {
+              methods.push({
+                methodName: 'name' in current.parent.key ? current.parent.key.name : undefined,
+                node,
+              });
+              break;
+            }
+
+            // Storing all variables containing this.state
+            if (current.type === 'VariableDeclarator') {
+              vars.push({
+                node,
+                scope: getScope(context, node),
+                variableName: 'name' in current.id ? current.id.name : undefined,
+              });
+              break;
+            }
+
+            current = current.parent;
+          }
+        }
+      },
+
+      Identifier(node) {
+        // Checks if the identifier is a variable within an object
+        /** @type {import('eslint').Rule.Node} */
+        let current = node;
+        while (current.parent.type === 'BinaryExpression') {
+          current = current.parent;
+        }
+        if (
+          ('value' in current.parent && current.parent.value === current)
+          || ('object' in current.parent && current.parent.object === current)
+        ) {
+          while (current.type !== 'Program') {
+            if (isFirstArgumentInSetStateCall(current, node)) {
+              vars
+                .filter((v) => v.scope === getScope(context, node) && v.variableName === node.name)
+                .forEach((v) => {
+                  report(context, messages.useCallback, 'useCallback', {
+                    node: v.node,
+                  });
+                });
+            }
+            current = current.parent;
+          }
+        }
+      },
+
+      ObjectPattern(node) {
+        const isDerivedFromThis = 'init' in node.parent && node.parent.init && node.parent.init.type === 'ThisExpression';
+        node.properties.forEach((property) => {
+          if (
+            property
+            && 'key' in property
+            && property.key
+            && 'name' in property.key
+            && property.key.name === 'state'
+            && isDerivedFromThis
+          ) {
+            vars.push({
+              node: property.key,
+              scope: getScope(context, node),
+              variableName: property.key.name,
+            });
+          }
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-adjacent-inline-elements.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-adjacent-inline-elements.d.ts","sourceRoot":"","sources":["no-adjacent-inline-elements.js"],"names":[],"mappings":"wBA+EW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-adjacent-inline-elements.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+/**
+ * @fileoverview Prevent adjacent inline elements not separated by whitespace.
+ * @author Sean Hayes
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+const astUtil = require('../util/ast');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
+const inlineNames = [
+  'a',
+  'b',
+  'big',
+  'i',
+  'small',
+  'tt',
+  'abbr',
+  'acronym',
+  'cite',
+  'code',
+  'dfn',
+  'em',
+  'kbd',
+  'strong',
+  'samp',
+  'time',
+  'var',
+  'bdo',
+  'br',
+  'img',
+  'map',
+  'object',
+  'q',
+  'script',
+  'span',
+  'sub',
+  'sup',
+  'button',
+  'input',
+  'label',
+  'select',
+  'textarea',
+];
+// Note: raw &nbsp; will be transformed into \u00a0.
+const whitespaceRegex = /(?:^\s|\s$)/;
+
+function isInline(node) {
+  if (node.type === 'Literal') {
+    // Regular whitespace will be removed.
+    const value = node.value;
+    // To properly separate inline elements, each end of the literal will need
+    // whitespace.
+    return !whitespaceRegex.test(value);
+  }
+  if (node.type === 'JSXElement' && inlineNames.indexOf(node.openingElement.name.name) > -1) {
+    return true;
+  }
+  if (astUtil.isCallExpression(node) && inlineNames.indexOf(node.arguments[0].value) > -1) {
+    return true;
+  }
+  return false;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  inlineElement: 'Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow adjacent inline elements not separated by whitespace.',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-adjacent-inline-elements'),
+    },
+    schema: [],
+
+    messages,
+  },
+  create(context) {
+    function validate(node, children) {
+      let currentIsInline = false;
+      let previousIsInline = false;
+      if (!children) {
+        return;
+      }
+      for (let i = 0; i < children.length; i++) {
+        currentIsInline = isInline(children[i]);
+        if (previousIsInline && currentIsInline) {
+          report(context, messages.inlineElement, 'inlineElement', {
+            node,
+          });
+          return;
+        }
+        previousIsInline = currentIsInline;
+      }
+    }
+    return {
+      JSXElement(node) {
+        validate(node, node.children);
+      },
+      CallExpression(node) {
+        if (!isCreateElement(context, node)) {
+          return;
+        }
+        if (node.arguments.length < 2 || !node.arguments[2]) {
+          return;
+        }
+        const children = 'elements' in node.arguments[2] ? node.arguments[2].elements : undefined;
+        validate(node, children);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-array-index-key.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-array-index-key.d.ts","sourceRoot":"","sources":["no-array-index-key.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-array-index-key.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,293 @@
+/**
+ * @fileoverview Prevent usage of Array index in keys
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+const has = require('hasown');
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const pragma = require('../util/pragma');
+const report = require('../util/report');
+const variableUtil = require('../util/variable');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function isCreateCloneElement(node, context) {
+  if (!node) {
+    return false;
+  }
+
+  if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') {
+    return node.object
+      && node.object.name === pragma.getFromContext(context)
+      && ['createElement', 'cloneElement'].indexOf(node.property.name) !== -1;
+  }
+
+  if (node.type === 'Identifier') {
+    const variable = variableUtil.findVariableByName(context, node, node.name);
+    if (variable && variable.type === 'ImportSpecifier') {
+      return variable.parent.source.value === 'react';
+    }
+  }
+
+  return false;
+}
+
+const messages = {
+  noArrayIndex: 'Do not use Array index in keys',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of Array index in keys',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-array-index-key'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+    const indexParamNames = [];
+    const iteratorFunctionsToIndexParamPosition = {
+      every: 1,
+      filter: 1,
+      find: 1,
+      findIndex: 1,
+      flatMap: 1,
+      forEach: 1,
+      map: 1,
+      reduce: 2,
+      reduceRight: 2,
+      some: 1,
+    };
+
+    function isArrayIndex(node) {
+      return node.type === 'Identifier'
+        && indexParamNames.indexOf(node.name) !== -1;
+    }
+
+    function isUsingReactChildren(node) {
+      const callee = node.callee;
+      if (
+        !callee
+        || !callee.property
+        || !callee.object
+      ) {
+        return null;
+      }
+
+      const isReactChildMethod = ['map', 'forEach'].indexOf(callee.property.name) > -1;
+      if (!isReactChildMethod) {
+        return null;
+      }
+
+      const obj = callee.object;
+      if (obj && obj.name === 'Children') {
+        return true;
+      }
+      if (obj && obj.object && obj.object.name === pragma.getFromContext(context)) {
+        return true;
+      }
+
+      return false;
+    }
+
+    function getMapIndexParamName(node) {
+      const callee = node.callee;
+      if (callee.type !== 'MemberExpression' && callee.type !== 'OptionalMemberExpression') {
+        return null;
+      }
+      if (callee.property.type !== 'Identifier') {
+        return null;
+      }
+      if (!has(iteratorFunctionsToIndexParamPosition, callee.property.name)) {
+        return null;
+      }
+
+      const name = /** @type {keyof iteratorFunctionsToIndexParamPosition} */ (callee.property.name);
+
+      const callbackArg = isUsingReactChildren(node)
+        ? node.arguments[1]
+        : node.arguments[0];
+
+      if (!callbackArg) {
+        return null;
+      }
+
+      if (!astUtil.isFunctionLikeExpression(callbackArg)) {
+        return null;
+      }
+
+      const params = callbackArg.params;
+
+      const indexParamPosition = iteratorFunctionsToIndexParamPosition[name];
+      if (params.length < indexParamPosition + 1) {
+        return null;
+      }
+
+      return params[indexParamPosition].name;
+    }
+
+    function getIdentifiersFromBinaryExpression(side) {
+      if (side.type === 'Identifier') {
+        return side;
+      }
+
+      if (side.type === 'BinaryExpression') {
+        // recurse
+        const left = getIdentifiersFromBinaryExpression(side.left);
+        const right = getIdentifiersFromBinaryExpression(side.right);
+        return [].concat(left, right).filter(Boolean);
+      }
+
+      return null;
+    }
+
+    function checkPropValue(node) {
+      if (isArrayIndex(node)) {
+        // key={bar}
+        report(context, messages.noArrayIndex, 'noArrayIndex', {
+          node,
+        });
+        return;
+      }
+
+      if (node.type === 'TemplateLiteral') {
+        // key={`foo-${bar}`}
+        node.expressions.filter(isArrayIndex).forEach(() => {
+          report(context, messages.noArrayIndex, 'noArrayIndex', {
+            node,
+          });
+        });
+
+        return;
+      }
+
+      if (node.type === 'BinaryExpression') {
+        // key={'foo' + bar}
+        const identifiers = getIdentifiersFromBinaryExpression(node);
+
+        identifiers.filter(isArrayIndex).forEach(() => {
+          report(context, messages.noArrayIndex, 'noArrayIndex', {
+            node,
+          });
+        });
+
+        return;
+      }
+
+      if (
+        astUtil.isCallExpression(node)
+        && node.callee
+        && node.callee.type === 'MemberExpression'
+        && node.callee.object
+        && isArrayIndex(node.callee.object)
+        && node.callee.property
+        && node.callee.property.type === 'Identifier'
+        && node.callee.property.name === 'toString'
+      ) {
+        // key={bar.toString()}
+        report(context, messages.noArrayIndex, 'noArrayIndex', {
+          node,
+        });
+        return;
+      }
+
+      if (
+        astUtil.isCallExpression(node)
+        && node.callee
+        && node.callee.type === 'Identifier'
+        && node.callee.name === 'String'
+        && Array.isArray(node.arguments)
+        && node.arguments.length > 0
+        && isArrayIndex(node.arguments[0])
+      ) {
+        // key={String(bar)}
+        report(context, messages.noArrayIndex, 'noArrayIndex', {
+          node: node.arguments[0],
+        });
+      }
+    }
+
+    function popIndex(node) {
+      const mapIndexParamName = getMapIndexParamName(node);
+      if (!mapIndexParamName) {
+        return;
+      }
+
+      indexParamNames.pop();
+    }
+
+    return {
+      'CallExpression, OptionalCallExpression'(node) {
+        if (isCreateCloneElement(node.callee, context) && node.arguments.length > 1) {
+          // React.createElement
+          if (!indexParamNames.length) {
+            return;
+          }
+
+          const props = node.arguments[1];
+
+          if (props.type !== 'ObjectExpression') {
+            return;
+          }
+
+          props.properties.forEach((prop) => {
+            if (!prop.key || prop.key.name !== 'key') {
+              // { ...foo }
+              // { foo: bar }
+              return;
+            }
+
+            checkPropValue(prop.value);
+          });
+
+          return;
+        }
+
+        const mapIndexParamName = getMapIndexParamName(node);
+        if (!mapIndexParamName) {
+          return;
+        }
+
+        indexParamNames.push(mapIndexParamName);
+      },
+
+      JSXAttribute(node) {
+        if (node.name.name !== 'key') {
+          // foo={bar}
+          return;
+        }
+
+        if (!indexParamNames.length) {
+          // Not inside a call expression that we think has an index param.
+          return;
+        }
+
+        const value = node.value;
+        if (!value || value.type !== 'JSXExpressionContainer') {
+          // key='foo' or just simply 'key'
+          return;
+        }
+
+        checkPropValue(value.expression);
+      },
+
+      'CallExpression:exit': popIndex,
+      'OptionalCallExpression:exit': popIndex,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-arrow-function-lifecycle.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-arrow-function-lifecycle.d.ts","sourceRoot":"","sources":["no-arrow-function-lifecycle.js"],"names":[],"mappings":"wBAsCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-arrow-function-lifecycle.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,149 @@
+/**
+ * @fileoverview Lifecycle methods should be methods on the prototype, not class fields
+ * @author Tan Nguyen
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const lifecycleMethods = require('../util/lifecycleMethods');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+function getRuleText(node) {
+  const params = node.value.params.map((p) => p.name);
+
+  if (node.type === 'Property') {
+    return `: function(${params.join(', ')}) `;
+  }
+
+  if (node.type === 'ClassProperty' || node.type === 'PropertyDefinition') {
+    return `(${params.join(', ')}) `;
+  }
+
+  return null;
+}
+
+const messages = {
+  lifecycle: '{{propertyName}} is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Lifecycle methods should be methods on the prototype, not class fields',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-arrow-function-lifecycle'),
+    },
+    messages,
+    schema: [],
+    fixable: 'code',
+  },
+
+  create: Components.detect((context, components) => {
+    /**
+     * @param {Array} properties list of component properties
+     */
+    function reportNoArrowFunctionLifecycle(properties) {
+      properties.forEach((node) => {
+        if (!node || !node.value) {
+          return;
+        }
+
+        const propertyName = astUtil.getPropertyName(node);
+        const nodeType = node.value.type;
+        const isLifecycleMethod = (
+          node.static && !componentUtil.isES5Component(node, context)
+            ? lifecycleMethods.static
+            : lifecycleMethods.instance
+        ).indexOf(propertyName) > -1;
+
+        if (nodeType === 'ArrowFunctionExpression' && isLifecycleMethod) {
+          const body = node.value.body;
+          const isBlockBody = body.type === 'BlockStatement';
+          const sourceCode = getSourceCode(context);
+
+          let nextComment = [];
+          let previousComment = [];
+          let bodyRange;
+          if (!isBlockBody) {
+            const previousToken = sourceCode.getTokenBefore(body);
+
+            if (sourceCode.getCommentsBefore) {
+              // eslint >=4.x
+              previousComment = sourceCode.getCommentsBefore(body);
+            } else {
+              // eslint 3.x
+              const potentialComment = sourceCode.getTokenBefore(body, { includeComments: true });
+              previousComment = previousToken === potentialComment ? [] : [potentialComment];
+            }
+
+            if (sourceCode.getCommentsAfter) {
+              // eslint >=4.x
+              nextComment = sourceCode.getCommentsAfter(body);
+            } else {
+              // eslint 3.x
+              const potentialComment = sourceCode.getTokenAfter(body, { includeComments: true });
+              const nextToken = sourceCode.getTokenAfter(body);
+              nextComment = nextToken === potentialComment ? [] : [potentialComment];
+            }
+            bodyRange = [
+              (previousComment.length > 0 ? previousComment[0] : body).range[0],
+              (nextComment.length > 0 ? nextComment[nextComment.length - 1] : body).range[1]
+                + (node.value.body.type === 'ObjectExpression' ? 1 : 0), // to account for a wrapped end paren
+            ];
+          }
+          const headRange = [
+            node.key.range[1],
+            (previousComment.length > 0 ? previousComment[0] : body).range[0],
+          ];
+          const hasSemi = node.value.expression && getText(context, node).slice(node.value.range[1] - node.range[0]) === ';';
+
+          report(
+            context,
+            messages.lifecycle,
+            'lifecycle',
+            {
+              node,
+              data: {
+                propertyName,
+              },
+              fix(fixer) {
+                if (!sourceCode.getCommentsAfter) {
+                  // eslint 3.x
+                  return isBlockBody && fixer.replaceTextRange(headRange, getRuleText(node));
+                }
+                return [].concat(
+                  fixer.replaceTextRange(headRange, getRuleText(node)),
+                  isBlockBody ? [] : fixer.replaceTextRange(
+                    [bodyRange[0], bodyRange[1] + (hasSemi ? 1 : 0)],
+                    `{ return ${previousComment.map((x) => getText(context, x)).join('')}${getText(context, body)}${nextComment.map((x) => getText(context, x)).join('')}; }`
+                  )
+                );
+              },
+            }
+          );
+        }
+      });
+    }
+
+    return {
+      'Program:exit'() {
+        values(components.list()).forEach((component) => {
+          const properties = astUtil.getComponentProperties(component.node);
+          reportNoArrowFunctionLifecycle(properties);
+        });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-children-prop.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-children-prop.d.ts","sourceRoot":"","sources":["no-children-prop.js"],"names":[],"mappings":"wBAuCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-children-prop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,125 @@
+/**
+ * @fileoverview Prevent passing of children as props
+ * @author Benjamin Stepp
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Checks if the node is a createElement call with a props literal.
+ * @param {ASTNode} node - The AST node being checked.
+ * @param {Context} context - The AST node being checked.
+ * @returns {boolean} - True if node is a createElement call with a props
+ * object literal, False if not.
+*/
+function isCreateElementWithProps(node, context) {
+  return isCreateElement(context, node)
+    && node.arguments.length > 1
+    && node.arguments[1].type === 'ObjectExpression';
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  nestChildren: 'Do not pass children as props. Instead, nest children between the opening and closing tags.',
+  passChildrenAsArgs: 'Do not pass children as props. Instead, pass them as additional arguments to React.createElement.',
+  nestFunction: 'Do not nest a function between the opening and closing tags. Instead, pass it as a prop.',
+  passFunctionAsArgs: 'Do not pass a function as an additional argument to React.createElement. Instead, pass it as a prop.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow passing of children as props',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-children-prop'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowFunctions: {
+          type: 'boolean',
+          default: false,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+  create(context) {
+    const configuration = context.options[0] || {};
+
+    function isFunction(node) {
+      return configuration.allowFunctions && (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression');
+    }
+
+    return {
+      JSXAttribute(node) {
+        if (node.name.name !== 'children') {
+          return;
+        }
+
+        const value = node.value;
+        if (value && value.type === 'JSXExpressionContainer' && isFunction(value.expression)) {
+          return;
+        }
+
+        report(context, messages.nestChildren, 'nestChildren', {
+          node,
+        });
+      },
+      CallExpression(node) {
+        if (!isCreateElementWithProps(node, context)) {
+          return;
+        }
+
+        const props = 'properties' in node.arguments[1] ? node.arguments[1].properties : undefined;
+        const childrenProp = props.find((prop) => (
+          'key' in prop
+          && prop.key
+          && 'name' in prop.key
+          && prop.key.name === 'children'
+        ));
+
+        if (childrenProp) {
+          if ('value' in childrenProp && childrenProp.value && !isFunction(childrenProp.value)) {
+            report(context, messages.passChildrenAsArgs, 'passChildrenAsArgs', {
+              node,
+            });
+          }
+        } else if (node.arguments.length === 3) {
+          const children = node.arguments[2];
+          if (isFunction(children)) {
+            report(context, messages.passFunctionAsArgs, 'passFunctionAsArgs', {
+              node,
+            });
+          }
+        }
+      },
+      JSXElement(node) {
+        const children = node.children;
+        if (children && children.length === 1 && children[0].type === 'JSXExpressionContainer') {
+          if (isFunction(children[0].expression)) {
+            report(context, messages.nestFunction, 'nestFunction', {
+              node,
+            });
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-danger-with-children.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-danger-with-children.d.ts","sourceRoot":"","sources":["no-danger-with-children.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger-with-children.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,157 @@
+/**
+ * @fileoverview Report when a DOM element is using both children and dangerouslySetInnerHTML
+ * @author David Petersen
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const jsxUtil = require('../util/jsx');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+const messages = {
+  dangerWithChildren: 'Only set one of `children` or `props.dangerouslySetInnerHTML`',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow when a DOM element is using both children and dangerouslySetInnerHTML',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('no-danger-with-children'),
+    },
+
+    messages,
+
+    schema: [], // no options
+  },
+  create(context) {
+    function findSpreadVariable(node, name) {
+      return variableUtil.getVariableFromContext(context, node, name);
+    }
+    /**
+     * Takes a ObjectExpression and returns the value of the prop if it has it
+     * @param {object} node - ObjectExpression node
+     * @param {string} propName - name of the prop to look for
+     * @param {string[]} seenProps
+     * @returns {object | boolean}
+     */
+    function findObjectProp(node, propName, seenProps) {
+      if (!node.properties) {
+        return false;
+      }
+      return node.properties.find((prop) => {
+        if (prop.type === 'Property') {
+          return prop.key.name === propName;
+        }
+        if (prop.type === 'ExperimentalSpreadProperty' || prop.type === 'SpreadElement') {
+          const variable = findSpreadVariable(node, prop.argument.name);
+          if (variable && variable.defs.length && variable.defs[0].node.init) {
+            if (seenProps.indexOf(prop.argument.name) > -1) {
+              return false;
+            }
+            const newSeenProps = seenProps.concat(prop.argument.name || []);
+            return findObjectProp(variable.defs[0].node.init, propName, newSeenProps);
+          }
+        }
+        return false;
+      });
+    }
+
+    /**
+     * Takes a JSXElement and returns the value of the prop if it has it
+     * @param {object} node - JSXElement node
+     * @param {string} propName - name of the prop to look for
+     * @returns {object | boolean}
+     */
+    function findJsxProp(node, propName) {
+      const attributes = node.openingElement.attributes;
+      return attributes.find((attribute) => {
+        if (attribute.type === 'JSXSpreadAttribute') {
+          const variable = findSpreadVariable(node, attribute.argument.name);
+          if (variable && variable.defs.length && variable.defs[0].node.init) {
+            return findObjectProp(variable.defs[0].node.init, propName, []);
+          }
+        }
+        return attribute.name && attribute.name.name === propName;
+      });
+    }
+
+    /**
+     * Checks to see if a node is a line break
+     * @param {ASTNode} node The AST node being checked
+     * @returns {boolean} True if node is a line break, false if not
+     */
+    function isLineBreak(node) {
+      const isLiteral = node.type === 'Literal' || node.type === 'JSXText';
+      const isMultiline = node.loc.start.line !== node.loc.end.line;
+      const isWhiteSpaces = jsxUtil.isWhiteSpaces(node.value);
+
+      return isLiteral && isMultiline && isWhiteSpaces;
+    }
+
+    return {
+      JSXElement(node) {
+        let hasChildren = false;
+
+        if (node.children.length && !isLineBreak(node.children[0])) {
+          hasChildren = true;
+        } else if (findJsxProp(node, 'children')) {
+          hasChildren = true;
+        }
+
+        if (
+          node.openingElement.attributes
+          && hasChildren
+          && findJsxProp(node, 'dangerouslySetInnerHTML')
+        ) {
+          report(context, messages.dangerWithChildren, 'dangerWithChildren', {
+            node,
+          });
+        }
+      },
+      CallExpression(node) {
+        if (
+          node.callee
+          && node.callee.type === 'MemberExpression'
+          && 'name' in node.callee.property
+          && node.callee.property.name === 'createElement'
+          && node.arguments.length > 1
+        ) {
+          let hasChildren = false;
+
+          let props = node.arguments[1];
+
+          if (props.type === 'Identifier') {
+            const variable = variableUtil.getVariableFromContext(context, node, props.name);
+            if (variable && variable.defs.length && variable.defs[0].node.init) {
+              props = variable.defs[0].node.init;
+            }
+          }
+
+          const dangerously = findObjectProp(props, 'dangerouslySetInnerHTML', []);
+
+          if (node.arguments.length === 2) {
+            if (findObjectProp(props, 'children', [])) {
+              hasChildren = true;
+            }
+          } else {
+            hasChildren = true;
+          }
+
+          if (dangerously && hasChildren) {
+            report(context, messages.dangerWithChildren, 'dangerWithChildren', {
+              node,
+            });
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-danger.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-danger.d.ts","sourceRoot":"","sources":["no-danger.js"],"names":[],"mappings":"wBA8CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-danger.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,96 @@
+/**
+ * @fileoverview Prevent usage of dangerous JSX props
+ * @author Scott Andrews
+ */
+
+'use strict';
+
+const has = require('hasown');
+const fromEntries = require('object.fromentries/polyfill')();
+const minimatch = require('minimatch');
+
+const docsUrl = require('../util/docsUrl');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DANGEROUS_PROPERTY_NAMES = [
+  'dangerouslySetInnerHTML',
+];
+
+const DANGEROUS_PROPERTIES = fromEntries(DANGEROUS_PROPERTY_NAMES.map((prop) => [prop, prop]));
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Checks if a JSX attribute is dangerous.
+ * @param {string} name - Name of the attribute to check.
+ * @returns {boolean} Whether or not the attribute is dangerous.
+ */
+function isDangerous(name) {
+  return has(DANGEROUS_PROPERTIES, name);
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  dangerousProp: 'Dangerous property \'{{name}}\' found',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of dangerous JSX properties',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-danger'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        customComponentNames: {
+          items: {
+            type: 'string',
+          },
+          minItems: 0,
+          type: 'array',
+          uniqueItems: true,
+        },
+      },
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const customComponentNames = configuration.customComponentNames || [];
+
+    return {
+      JSXAttribute(node) {
+        const nodeName = node.parent.name;
+        const functionName = nodeName.name || `${nodeName.object.name}.${nodeName.property.name}`;
+
+        const enableCheckingCustomComponent = customComponentNames.some((name) => minimatch(functionName, name));
+
+        if ((enableCheckingCustomComponent || jsxUtil.isDOMComponent(node.parent)) && isDangerous(node.name.name)) {
+          report(context, messages.dangerousProp, 'dangerousProp', {
+            node,
+            data: {
+              name: node.name.name,
+            },
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-deprecated.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-deprecated.d.ts","sourceRoot":"","sources":["no-deprecated.js"],"names":[],"mappings":"wBAqHW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-deprecated.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,271 @@
+/**
+ * @fileoverview Prevent usage of deprecated methods
+ * @author Yannick Croissant
+ * @author Scott Feeney
+ * @author Sergei Startsev
+ */
+
+'use strict';
+
+const entries = require('object.entries');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const pragmaUtil = require('../util/pragma');
+const testReactVersion = require('../util/version').testReactVersion;
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const MODULES = {
+  react: ['React'],
+  'react-addons-perf': ['ReactPerf', 'Perf'],
+  'react-dom': ['ReactDOM'],
+  'react-dom/server': ['ReactDOMServer'],
+};
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function getDeprecated(pragma) {
+  const deprecated = {};
+  // 0.12.0
+  deprecated[`${pragma}.renderComponent`] = ['0.12.0', `${pragma}.render`];
+  deprecated[`${pragma}.renderComponentToString`] = ['0.12.0', `${pragma}.renderToString`];
+  deprecated[`${pragma}.renderComponentToStaticMarkup`] = ['0.12.0', `${pragma}.renderToStaticMarkup`];
+  deprecated[`${pragma}.isValidComponent`] = ['0.12.0', `${pragma}.isValidElement`];
+  deprecated[`${pragma}.PropTypes.component`] = ['0.12.0', `${pragma}.PropTypes.element`];
+  deprecated[`${pragma}.PropTypes.renderable`] = ['0.12.0', `${pragma}.PropTypes.node`];
+  deprecated[`${pragma}.isValidClass`] = ['0.12.0'];
+  deprecated['this.transferPropsTo'] = ['0.12.0', 'spread operator ({...})'];
+  // 0.13.0
+  deprecated[`${pragma}.addons.classSet`] = ['0.13.0', 'the npm module classnames'];
+  deprecated[`${pragma}.addons.cloneWithProps`] = ['0.13.0', `${pragma}.cloneElement`];
+  // 0.14.0
+  deprecated[`${pragma}.render`] = ['0.14.0', 'ReactDOM.render'];
+  deprecated[`${pragma}.unmountComponentAtNode`] = ['0.14.0', 'ReactDOM.unmountComponentAtNode'];
+  deprecated[`${pragma}.findDOMNode`] = ['0.14.0', 'ReactDOM.findDOMNode'];
+  deprecated[`${pragma}.renderToString`] = ['0.14.0', 'ReactDOMServer.renderToString'];
+  deprecated[`${pragma}.renderToStaticMarkup`] = ['0.14.0', 'ReactDOMServer.renderToStaticMarkup'];
+  // 15.0.0
+  deprecated[`${pragma}.addons.LinkedStateMixin`] = ['15.0.0'];
+  deprecated['ReactPerf.printDOM'] = ['15.0.0', 'ReactPerf.printOperations'];
+  deprecated['Perf.printDOM'] = ['15.0.0', 'Perf.printOperations'];
+  deprecated['ReactPerf.getMeasurementsSummaryMap'] = ['15.0.0', 'ReactPerf.getWasted'];
+  deprecated['Perf.getMeasurementsSummaryMap'] = ['15.0.0', 'Perf.getWasted'];
+  // 15.5.0
+  deprecated[`${pragma}.createClass`] = ['15.5.0', 'the npm module create-react-class'];
+  deprecated[`${pragma}.addons.TestUtils`] = ['15.5.0', 'ReactDOM.TestUtils'];
+  deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types'];
+  // 15.6.0
+  deprecated[`${pragma}.DOM`] = ['15.6.0', 'the npm module react-dom-factories'];
+  // 16.9.0
+  // For now the following life-cycle methods are just legacy, not deprecated:
+  // `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate`
+  // https://github.com/yannickcr/eslint-plugin-react/pull/1750#issuecomment-425975934
+  deprecated.componentWillMount = [
+    '16.9.0',
+    'UNSAFE_componentWillMount',
+    'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount. '
+    + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
+  ];
+  deprecated.componentWillReceiveProps = [
+    '16.9.0',
+    'UNSAFE_componentWillReceiveProps',
+    'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops. '
+    + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
+  ];
+  deprecated.componentWillUpdate = [
+    '16.9.0',
+    'UNSAFE_componentWillUpdate',
+    'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate. '
+    + 'Use https://github.com/reactjs/react-codemod#rename-unsafe-lifecycles to automatically update your components.',
+  ];
+  // 18.0.0
+  // https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
+  deprecated['ReactDOM.render'] = [
+    '18.0.0',
+    'createRoot',
+    'https://reactjs.org/link/switch-to-createroot',
+  ];
+  deprecated['ReactDOM.hydrate'] = [
+    '18.0.0',
+    'hydrateRoot',
+    'https://reactjs.org/link/switch-to-createroot',
+  ];
+  deprecated['ReactDOM.unmountComponentAtNode'] = [
+    '18.0.0',
+    'root.unmount',
+    'https://reactjs.org/link/switch-to-createroot',
+  ];
+  deprecated['ReactDOMServer.renderToNodeStream'] = [
+    '18.0.0',
+    'renderToPipeableStream',
+    'https://reactjs.org/docs/react-dom-server.html#rendertonodestream',
+  ];
+
+  return deprecated;
+}
+
+const messages = {
+  deprecated: '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}{{refs}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of deprecated methods',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-deprecated'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    const pragma = pragmaUtil.getFromContext(context);
+    const deprecated = getDeprecated(pragma);
+
+    function isDeprecated(method) {
+      return (
+        deprecated
+        && deprecated[method]
+        && deprecated[method][0]
+        && testReactVersion(context, `>= ${deprecated[method][0]}`)
+      );
+    }
+
+    function checkDeprecation(node, methodName, methodNode) {
+      if (!isDeprecated(methodName)) {
+        return;
+      }
+      const version = deprecated[methodName][0];
+      const newMethod = deprecated[methodName][1];
+      const refs = deprecated[methodName][2];
+      report(context, messages.deprecated, 'deprecated', {
+        node: methodNode || node,
+        data: {
+          oldMethod: methodName,
+          version,
+          newMethod: newMethod ? `, use ${newMethod} instead` : '',
+          refs: refs ? `, see ${refs}` : '',
+        },
+      });
+    }
+
+    function getReactModuleName(node) {
+      let moduleName = false;
+      if (!node.init) {
+        return false;
+      }
+
+      entries(MODULES).some((entry) => {
+        const key = entry[0];
+        const moduleNames = entry[1];
+        if (
+          node.init.arguments
+          && node.init.arguments.length > 0
+          && node.init.arguments[0]
+          && key === node.init.arguments[0].value
+        ) {
+          moduleName = MODULES[key][0];
+        } else {
+          moduleName = moduleNames.find((name) => name === node.init.name);
+        }
+        return moduleName;
+      });
+
+      return moduleName;
+    }
+
+    /**
+     * Returns life cycle methods if available
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {Array} The array of methods.
+     */
+    function getLifeCycleMethods(node) {
+      const properties = astUtil.getComponentProperties(node);
+      return properties.map((property) => ({
+        name: astUtil.getPropertyName(property),
+        node: astUtil.getPropertyNameNode(property),
+      }));
+    }
+
+    /**
+     * Checks life cycle methods
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function checkLifeCycleMethods(node) {
+      if (
+        componentUtil.isES5Component(node, context)
+     || componentUtil.isES6Component(node, context)
+      ) {
+        const methods = getLifeCycleMethods(node);
+        methods.forEach((method) => checkDeprecation(node, method.name, method.node));
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      MemberExpression(node) {
+        checkDeprecation(node, getText(context, node));
+      },
+
+      ImportDeclaration(node) {
+        const isReactImport = typeof MODULES[node.source.value] !== 'undefined';
+        if (!isReactImport) {
+          return;
+        }
+        node.specifiers.filter(((s) => 'imported' in s && s.imported)).forEach((specifier) => {
+          // TODO, semver-major: remove `in` check as part of jsdoc->tsdoc migration
+          checkDeprecation(node, 'imported' in specifier && 'name' in specifier.imported && `${MODULES[node.source.value][0]}.${specifier.imported.name}`, specifier);
+        });
+      },
+
+      VariableDeclarator(node) {
+        const reactModuleName = getReactModuleName(node);
+        const isRequire = node.init
+          && 'callee' in node.init
+          && node.init.callee
+          && 'name' in node.init.callee
+          && node.init.callee.name === 'require';
+        const isReactRequire = node.init
+          && 'arguments' in node.init
+          && node.init.arguments
+          && node.init.arguments.length
+          && typeof MODULES['value' in node.init.arguments[0] ? node.init.arguments[0].value : undefined] !== 'undefined';
+        const isDestructuring = node.id && node.id.type === 'ObjectPattern';
+
+        if (
+          !(isDestructuring && reactModuleName)
+          && !(isDestructuring && isRequire && isReactRequire)
+        ) {
+          return;
+        }
+
+        ('properties' in node.id ? node.id.properties : undefined).filter((p) => p.type !== 'RestElement' && p.key).forEach((property) => {
+          checkDeprecation(
+            node,
+            'key' in property && 'name' in property.key && `${reactModuleName || pragma}.${property.key.name}`,
+            property
+          );
+        });
+      },
+
+      ClassDeclaration: checkLifeCycleMethods,
+      ClassExpression: checkLifeCycleMethods,
+      ObjectExpression: checkLifeCycleMethods,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-did-mount-set-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-did-mount-set-state.d.ts","sourceRoot":"","sources":["no-did-mount-set-state.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-mount-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+/**
+ * @fileoverview Prevent usage of setState in componentDidMount
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = makeNoMethodSetStateRule('componentDidMount');
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-did-update-set-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-did-update-set-state.d.ts","sourceRoot":"","sources":["no-did-update-set-state.js"],"names":[],"mappings":"wBASW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-did-update-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,11 @@
+/**
+ * @fileoverview Prevent usage of setState in componentDidUpdate
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = makeNoMethodSetStateRule('componentDidUpdate');
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-direct-mutation-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-direct-mutation-state.d.ts","sourceRoot":"","sources":["no-direct-mutation-state.js"],"names":[],"mappings":"wBAuBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-direct-mutation-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,155 @@
+/**
+ * @fileoverview Prevent direct mutation of this.state
+ * @author David Petersen
+ * @author Nicolas Fernandez <@burabure>
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noDirectMutation: 'Do not mutate state directly. Use setState().',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow direct mutation of this.state',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('no-direct-mutation-state'),
+    },
+
+    messages,
+  },
+
+  create: Components.detect((context, components, utils) => {
+    /**
+     * Checks if the component is valid
+     * @param {Object} component The component to process
+     * @returns {boolean} True if the component is valid, false if not.
+     */
+    function isValid(component) {
+      return !!component && !component.mutateSetState;
+    }
+
+    /**
+     * Reports undeclared proptypes for a given component
+     * @param {Object} component The component to process
+     */
+    function reportMutations(component) {
+      let mutation;
+      for (let i = 0, j = component.mutations.length; i < j; i++) {
+        mutation = component.mutations[i];
+        report(context, messages.noDirectMutation, 'noDirectMutation', {
+          node: mutation,
+        });
+      }
+    }
+
+    /**
+     * Walks through the MemberExpression to the top-most property.
+     * @param {Object} node The node to process
+     * @returns {Object} The outer-most MemberExpression
+     */
+    function getOuterMemberExpression(node) {
+      while (node.object && node.object.property) {
+        node = node.object;
+      }
+      return node;
+    }
+
+    /**
+     * Determine if we should currently ignore assignments in this component.
+     * @param {?Object} component The component to process
+     * @returns {boolean} True if we should skip assignment checks.
+     */
+    function shouldIgnoreComponent(component) {
+      return !component || (component.inConstructor && !component.inCallExpression);
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+    return {
+      MethodDefinition(node) {
+        if (node.kind === 'constructor') {
+          components.set(node, {
+            inConstructor: true,
+          });
+        }
+      },
+
+      CallExpression(node) {
+        components.set(node, {
+          inCallExpression: true,
+        });
+      },
+
+      AssignmentExpression(node) {
+        const component = components.get(utils.getParentComponent(node));
+        if (shouldIgnoreComponent(component) || !node.left || !node.left.object) {
+          return;
+        }
+        const item = getOuterMemberExpression(node.left);
+        if (componentUtil.isStateMemberExpression(item)) {
+          const mutations = (component && component.mutations) || [];
+          mutations.push(node.left.object);
+          components.set(node, {
+            mutateSetState: true,
+            mutations,
+          });
+        }
+      },
+
+      UpdateExpression(node) {
+        const component = components.get(utils.getParentComponent(node));
+        if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') {
+          return;
+        }
+        const item = getOuterMemberExpression(node.argument);
+        if (componentUtil.isStateMemberExpression(item)) {
+          const mutations = (component && component.mutations) || [];
+          mutations.push(item);
+          components.set(node, {
+            mutateSetState: true,
+            mutations,
+          });
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        components.set(node, {
+          inCallExpression: false,
+        });
+      },
+
+      'MethodDefinition:exit'(node) {
+        if (node.kind === 'constructor') {
+          components.set(node, {
+            inConstructor: false,
+          });
+        }
+      },
+
+      'Program:exit'() {
+        values(components.list())
+          .filter((component) => !isValid(component))
+          .forEach((component) => {
+            reportMutations(component);
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-find-dom-node.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-find-dom-node.d.ts","sourceRoot":"","sources":["no-find-dom-node.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-find-dom-node.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Prevent usage of findDOMNode
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noFindDOMNode: 'Do not use findDOMNode. It doesn’t work with function components and is deprecated in StrictMode. See https://reactjs.org/docs/react-dom.html#finddomnode',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of findDOMNode',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-find-dom-node'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const callee = node.callee;
+
+        const isFindDOMNode = ('name' in callee && callee.name === 'findDOMNode') || (
+          'property' in callee
+          && callee.property
+          && 'name' in callee.property
+          && callee.property.name === 'findDOMNode'
+        );
+
+        if (!isFindDOMNode) {
+          return;
+        }
+
+        report(context, messages.noFindDOMNode, 'noFindDOMNode', {
+          node: callee,
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-invalid-html-attribute.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-invalid-html-attribute.d.ts","sourceRoot":"","sources":["no-invalid-html-attribute.js"],"names":[],"mappings":"wBA+kBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-invalid-html-attribute.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,654 @@
+/**
+ * @fileoverview Check if tag attributes to have non-valid value
+ * @author Sebastian Malton
+ */
+
+'use strict';
+
+const matchAll = require('string.prototype.matchall');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const rel = new Map([
+  ['alternate', new Set(['link', 'area', 'a'])],
+  ['apple-touch-icon', new Set(['link'])],
+  ['apple-touch-startup-image', new Set(['link'])],
+  ['author', new Set(['link', 'area', 'a'])],
+  ['bookmark', new Set(['area', 'a'])],
+  ['canonical', new Set(['link'])],
+  ['dns-prefetch', new Set(['link'])],
+  ['external', new Set(['area', 'a', 'form'])],
+  ['help', new Set(['link', 'area', 'a', 'form'])],
+  ['icon', new Set(['link'])],
+  ['license', new Set(['link', 'area', 'a', 'form'])],
+  ['manifest', new Set(['link'])],
+  ['mask-icon', new Set(['link'])],
+  ['modulepreload', new Set(['link'])],
+  ['next', new Set(['link', 'area', 'a', 'form'])],
+  ['nofollow', new Set(['area', 'a', 'form'])],
+  ['noopener', new Set(['area', 'a', 'form'])],
+  ['noreferrer', new Set(['area', 'a', 'form'])],
+  ['opener', new Set(['area', 'a', 'form'])],
+  ['pingback', new Set(['link'])],
+  ['preconnect', new Set(['link'])],
+  ['prefetch', new Set(['link'])],
+  ['preload', new Set(['link'])],
+  ['prerender', new Set(['link'])],
+  ['prev', new Set(['link', 'area', 'a', 'form'])],
+  ['search', new Set(['link', 'area', 'a', 'form'])],
+  ['shortcut', new Set(['link'])], // generally allowed but needs pair with "icon"
+  ['shortcut\u0020icon', new Set(['link'])],
+  ['stylesheet', new Set(['link'])],
+  ['tag', new Set(['area', 'a'])],
+]);
+
+const pairs = new Map([
+  ['shortcut', new Set(['icon'])],
+]);
+
+/**
+ * Map between attributes and a mapping between valid values and a set of tags they are valid on
+ * @type {Map<string, Map<string, Set<string>>>}
+ */
+const VALID_VALUES = new Map([
+  ['rel', rel],
+]);
+
+/**
+ * Map between attributes and a mapping between pair-values and a set of values they are valid with
+ * @type {Map<string, Map<string, Set<string>>>}
+ */
+const VALID_PAIR_VALUES = new Map([
+  ['rel', pairs],
+]);
+
+/**
+ * The set of all possible HTML elements. Used for skipping custom types
+ * @type {Set<string>}
+ */
+const HTML_ELEMENTS = new Set([
+  'a',
+  'abbr',
+  'acronym',
+  'address',
+  'applet',
+  'area',
+  'article',
+  'aside',
+  'audio',
+  'b',
+  'base',
+  'basefont',
+  'bdi',
+  'bdo',
+  'bgsound',
+  'big',
+  'blink',
+  'blockquote',
+  'body',
+  'br',
+  'button',
+  'canvas',
+  'caption',
+  'center',
+  'cite',
+  'code',
+  'col',
+  'colgroup',
+  'content',
+  'data',
+  'datalist',
+  'dd',
+  'del',
+  'details',
+  'dfn',
+  'dialog',
+  'dir',
+  'div',
+  'dl',
+  'dt',
+  'em',
+  'embed',
+  'fieldset',
+  'figcaption',
+  'figure',
+  'font',
+  'footer',
+  'form',
+  'frame',
+  'frameset',
+  'h1',
+  'h2',
+  'h3',
+  'h4',
+  'h5',
+  'h6',
+  'head',
+  'header',
+  'hgroup',
+  'hr',
+  'html',
+  'i',
+  'iframe',
+  'image',
+  'img',
+  'input',
+  'ins',
+  'kbd',
+  'keygen',
+  'label',
+  'legend',
+  'li',
+  'link',
+  'main',
+  'map',
+  'mark',
+  'marquee',
+  'math',
+  'menu',
+  'menuitem',
+  'meta',
+  'meter',
+  'nav',
+  'nobr',
+  'noembed',
+  'noframes',
+  'noscript',
+  'object',
+  'ol',
+  'optgroup',
+  'option',
+  'output',
+  'p',
+  'param',
+  'picture',
+  'plaintext',
+  'portal',
+  'pre',
+  'progress',
+  'q',
+  'rb',
+  'rp',
+  'rt',
+  'rtc',
+  'ruby',
+  's',
+  'samp',
+  'script',
+  'section',
+  'select',
+  'shadow',
+  'slot',
+  'small',
+  'source',
+  'spacer',
+  'span',
+  'strike',
+  'strong',
+  'style',
+  'sub',
+  'summary',
+  'sup',
+  'svg',
+  'table',
+  'tbody',
+  'td',
+  'template',
+  'textarea',
+  'tfoot',
+  'th',
+  'thead',
+  'time',
+  'title',
+  'tr',
+  'track',
+  'tt',
+  'u',
+  'ul',
+  'var',
+  'video',
+  'wbr',
+  'xmp',
+]);
+
+/**
+* Map between attributes and set of tags that the attribute is valid on
+* @type {Map<string, Set<string>>}
+*/
+const COMPONENT_ATTRIBUTE_MAP = new Map([
+  ['rel', new Set(['link', 'a', 'area', 'form'])],
+]);
+
+/* eslint-disable eslint-plugin/no-unused-message-ids -- false positives, these messageIds are used */
+const messages = {
+  emptyIsMeaningless: 'An empty “{{attributeName}}” attribute is meaningless.',
+  neverValid: '“{{reportingValue}}” is never a valid “{{attributeName}}” attribute value.',
+  noEmpty: 'An empty “{{attributeName}}” attribute is meaningless.',
+  noMethod: 'The ”{{attributeName}}“ attribute cannot be a method.',
+  notAlone: '“{{reportingValue}}” must be directly followed by “{{missingValue}}”.',
+  notPaired: '“{{reportingValue}}” can not be directly followed by “{{secondValue}}” without “{{missingValue}}”.',
+  notValidFor: '“{{reportingValue}}” is not a valid “{{attributeName}}” attribute value for <{{elementName}}>.',
+  onlyMeaningfulFor: 'The ”{{attributeName}}“ attribute only has meaning on the tags: {{tagNames}}',
+  onlyStrings: '“{{attributeName}}” attribute only supports strings.',
+  spaceDelimited: '”{{attributeName}}“ attribute values should be space delimited.',
+  suggestRemoveDefault: '"remove {{attributeName}}"',
+  suggestRemoveEmpty: '"remove empty attribute {{attributeName}}"',
+  suggestRemoveInvalid: '“remove invalid attribute {{reportingValue}}”',
+  suggestRemoveWhitespaces: 'remove whitespaces in “{{attributeName}}”',
+  suggestRemoveNonString: 'remove non-string value in “{{attributeName}}”',
+};
+
+function splitIntoRangedParts(node, regex) {
+  const valueRangeStart = node.range[0] + 1; // the plus one is for the initial quote
+
+  return Array.from(matchAll(node.value, regex), (match) => {
+    const start = match.index + valueRangeStart;
+    const end = start + match[0].length;
+
+    return {
+      reportingValue: `${match[1]}`,
+      value: match[1],
+      range: [start, end],
+    };
+  });
+}
+
+function checkLiteralValueNode(context, attributeName, node, parentNode, parentNodeName) {
+  if (typeof node.value !== 'string') {
+    const data = { attributeName, reportingValue: node.value };
+
+    report(context, messages.onlyStrings, 'onlyStrings', {
+      node,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveNonString',
+        data,
+        fix(fixer) { return fixer.remove(parentNode); },
+      }],
+    });
+    return;
+  }
+
+  if (!node.value.trim()) {
+    const data = { attributeName, reportingValue: node.value };
+
+    report(context, messages.noEmpty, 'noEmpty', {
+      node,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveEmpty',
+        data,
+        fix(fixer) { return fixer.remove(node.parent); },
+      }],
+    });
+    return;
+  }
+
+  const singleAttributeParts = splitIntoRangedParts(node, /(\S+)/g);
+  singleAttributeParts.forEach((singlePart) => {
+    const allowedTags = VALID_VALUES.get(attributeName).get(singlePart.value);
+    const reportingValue = singlePart.reportingValue;
+
+    if (!allowedTags) {
+      const data = {
+        attributeName,
+        reportingValue,
+      };
+
+      const suggest = [{
+        messageId: 'suggestRemoveInvalid',
+        data,
+        fix(fixer) { return fixer.removeRange(singlePart.range); },
+      }];
+
+      report(context, messages.neverValid, 'neverValid', {
+        node,
+        data,
+        suggest,
+      });
+    } else if (!allowedTags.has(parentNodeName)) {
+      const data = {
+        attributeName,
+        reportingValue,
+        elementName: parentNodeName,
+      };
+
+      const suggest = [{
+        messageId: 'suggestRemoveInvalid',
+        data,
+        fix(fixer) { return fixer.removeRange(singlePart.range); },
+      }];
+
+      report(context, messages.notValidFor, 'notValidFor', {
+        node,
+        data,
+        suggest,
+      });
+    }
+  });
+
+  const allowedPairsForAttribute = VALID_PAIR_VALUES.get(attributeName);
+  if (allowedPairsForAttribute) {
+    const pairAttributeParts = splitIntoRangedParts(node, /(?=(\b\S+\s*\S+))/g);
+    pairAttributeParts.forEach((pairPart) => {
+      allowedPairsForAttribute.forEach((siblings, pairing) => {
+        const attributes = pairPart.reportingValue.split('\u0020');
+        const firstValue = attributes[0];
+        const secondValue = attributes[1];
+        if (firstValue === pairing) {
+          const lastValue = attributes[attributes.length - 1]; // in case of multiple white spaces
+          if (!siblings.has(lastValue)) {
+            const message = secondValue ? messages.notPaired : messages.notAlone;
+            const messageId = secondValue ? 'notPaired' : 'notAlone';
+            report(context, message, messageId, {
+              node,
+              data: {
+                reportingValue: firstValue,
+                secondValue,
+                missingValue: Array.from(siblings).join(', '),
+              },
+              suggest: false,
+            });
+          }
+        }
+      });
+    });
+  }
+
+  const whitespaceParts = splitIntoRangedParts(node, /(\s+)/g);
+  whitespaceParts.forEach((whitespacePart) => {
+    const data = { attributeName };
+
+    if (whitespacePart.range[0] === (node.range[0] + 1) || whitespacePart.range[1] === (node.range[1] - 1)) {
+      report(context, messages.spaceDelimited, 'spaceDelimited', {
+        node,
+        data,
+        suggest: [{
+          messageId: 'suggestRemoveWhitespaces',
+          data,
+          fix(fixer) { return fixer.removeRange(whitespacePart.range); },
+        }],
+      });
+    } else if (whitespacePart.value !== '\u0020') {
+      report(context, messages.spaceDelimited, 'spaceDelimited', {
+        node,
+        data,
+        suggest: [{
+          messageId: 'suggestRemoveWhitespaces',
+          data,
+          fix(fixer) { return fixer.replaceTextRange(whitespacePart.range, '\u0020'); },
+        }],
+      });
+    }
+  });
+}
+
+const DEFAULT_ATTRIBUTES = ['rel'];
+
+function checkAttribute(context, node) {
+  const attribute = node.name.name;
+
+  const parentNodeName = node.parent.name.name;
+  if (!COMPONENT_ATTRIBUTE_MAP.has(attribute) || !COMPONENT_ATTRIBUTE_MAP.get(attribute).has(parentNodeName)) {
+    const tagNames = Array.from(
+      COMPONENT_ATTRIBUTE_MAP.get(attribute).values(),
+      (tagName) => `"<${tagName}>"`
+    ).join(', ');
+    const data = {
+      attributeName: attribute,
+      tagNames,
+    };
+
+    report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', {
+      node: node.name,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveDefault',
+        data,
+        fix(fixer) { return fixer.remove(node); },
+      }],
+    });
+    return;
+  }
+
+  function fix(fixer) { return fixer.remove(node); }
+
+  if (!node.value) {
+    const data = { attributeName: attribute };
+
+    report(context, messages.emptyIsMeaningless, 'emptyIsMeaningless', {
+      node: node.name,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveEmpty',
+        data,
+        fix,
+      }],
+    });
+    return;
+  }
+
+  if (node.value.type === 'Literal') {
+    return checkLiteralValueNode(context, attribute, node.value, node, parentNodeName);
+  }
+
+  if (node.value.expression.type === 'Literal') {
+    return checkLiteralValueNode(context, attribute, node.value.expression, node, parentNodeName);
+  }
+
+  if (node.value.type !== 'JSXExpressionContainer') {
+    return;
+  }
+
+  if (node.value.expression.type === 'ObjectExpression') {
+    const data = { attributeName: attribute };
+
+    report(context, messages.onlyStrings, 'onlyStrings', {
+      node: node.value,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveDefault',
+        data,
+        fix,
+      }],
+    });
+  } else if (node.value.expression.type === 'Identifier' && node.value.expression.name === 'undefined') {
+    const data = { attributeName: attribute };
+
+    report(context, messages.onlyStrings, 'onlyStrings', {
+      node: node.value,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveDefault',
+        data,
+        fix,
+      }],
+    });
+  }
+}
+
+function isValidCreateElement(node) {
+  return node.callee
+    && node.callee.type === 'MemberExpression'
+    && node.callee.object.name === 'React'
+    && node.callee.property.name === 'createElement'
+    && node.arguments.length > 0;
+}
+
+function checkPropValidValue(context, node, value, attribute) {
+  const validTags = VALID_VALUES.get(attribute);
+
+  if (value.type !== 'Literal') {
+    return; // cannot check non-literals
+  }
+
+  const validTagSet = validTags.get(value.value);
+  if (!validTagSet) {
+    const data = {
+      attributeName: attribute,
+      reportingValue: value.value,
+    };
+
+    report(context, messages.neverValid, 'neverValid', {
+      node: value,
+      data,
+      suggest: [{
+        messageId: 'suggestRemoveInvalid',
+        data,
+        fix(fixer) { return fixer.replaceText(value, value.raw.replace(value.value, '')); },
+      }],
+    });
+  } else if (!validTagSet.has(node.arguments[0].value)) {
+    report(context, messages.notValidFor, 'notValidFor', {
+      node: value,
+      data: {
+        attributeName: attribute,
+        reportingValue: value.raw,
+        elementName: node.arguments[0].value,
+      },
+      suggest: false,
+    });
+  }
+}
+
+/**
+ *
+ * @param {*} context
+ * @param {*} node
+ * @param {string} attribute
+ */
+function checkCreateProps(context, node, attribute) {
+  const propsArg = node.arguments[1];
+
+  if (!propsArg || propsArg.type !== 'ObjectExpression') {
+    return; // can't check variables, computed, or shorthands
+  }
+
+  for (const prop of propsArg.properties) {
+    if (!prop.key || prop.key.type !== 'Identifier') {
+      // eslint-disable-next-line no-continue
+      continue; // cannot check computed keys
+    }
+
+    if (prop.key.name !== attribute) {
+      // eslint-disable-next-line no-continue
+      continue; // ignore not this attribute
+    }
+
+    if (!COMPONENT_ATTRIBUTE_MAP.get(attribute).has(node.arguments[0].value)) {
+      const tagNames = Array.from(
+        COMPONENT_ATTRIBUTE_MAP.get(attribute).values(),
+        (tagName) => `"<${tagName}>"`
+      ).join(', ');
+
+      report(context, messages.onlyMeaningfulFor, 'onlyMeaningfulFor', {
+        node: prop.key,
+        data: {
+          attributeName: attribute,
+          tagNames,
+        },
+        suggest: false,
+      });
+
+      // eslint-disable-next-line no-continue
+      continue;
+    }
+
+    if (prop.method) {
+      report(context, messages.noMethod, 'noMethod', {
+        node: prop,
+        data: {
+          attributeName: attribute,
+        },
+        suggest: false,
+      });
+
+      // eslint-disable-next-line no-continue
+      continue;
+    }
+
+    if (prop.shorthand || prop.computed) {
+      // eslint-disable-next-line no-continue
+      continue; // cannot check these
+    }
+
+    if (prop.value.type === 'ArrayExpression') {
+      prop.value.elements.forEach((value) => {
+        checkPropValidValue(context, node, value, attribute);
+      });
+
+      // eslint-disable-next-line no-continue
+      continue;
+    }
+
+    checkPropValidValue(context, node, prop.value, attribute);
+  }
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of invalid attributes',
+      category: 'Possible Errors',
+      url: docsUrl('no-invalid-html-attribute'),
+    },
+    messages,
+    schema: [{
+      type: 'array',
+      uniqueItems: true,
+      items: {
+        enum: ['rel'],
+      },
+    }],
+    type: 'suggestion',
+    hasSuggestions: true, // eslint-disable-line eslint-plugin/require-meta-has-suggestions
+  },
+
+  create(context) {
+    return {
+      JSXAttribute(node) {
+        const attributes = new Set(context.options[0] || DEFAULT_ATTRIBUTES);
+
+        // ignore attributes that aren't configured to be checked
+        if (!attributes.has(node.name.name)) {
+          return;
+        }
+
+        // ignore non-HTML elements
+        if (!HTML_ELEMENTS.has(node.parent.name.name)) {
+          return;
+        }
+
+        checkAttribute(context, node);
+      },
+
+      CallExpression(node) {
+        if (!isValidCreateElement(node)) {
+          return;
+        }
+
+        const elemNameArg = node.arguments[0];
+
+        if (!elemNameArg || elemNameArg.type !== 'Literal') {
+          return; // can only check literals
+        }
+
+        // ignore non-HTML elements
+        if (typeof elemNameArg.value === 'string' && !HTML_ELEMENTS.has(elemNameArg.value)) {
+          return;
+        }
+
+        const attributes = new Set(context.options[0] || DEFAULT_ATTRIBUTES);
+
+        attributes.forEach((attribute) => {
+          checkCreateProps(context, node, attribute);
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-is-mounted.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-is-mounted.d.ts","sourceRoot":"","sources":["no-is-mounted.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-is-mounted.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Prevent usage of isMounted
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getAncestors = require('../util/eslint').getAncestors;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noIsMounted: 'Do not use isMounted',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of isMounted',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-is-mounted'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const callee = node.callee;
+        if (callee.type !== 'MemberExpression') {
+          return;
+        }
+        if (
+          callee.object.type !== 'ThisExpression'
+          || !('name' in callee.property)
+          || callee.property.name !== 'isMounted'
+        ) {
+          return;
+        }
+        const ancestors = getAncestors(context, node);
+        for (let i = 0, j = ancestors.length; i < j; i++) {
+          if (ancestors[i].type === 'Property' || ancestors[i].type === 'MethodDefinition') {
+            report(context, messages.noIsMounted, 'noIsMounted', {
+              node: callee,
+            });
+            break;
+          }
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-multi-comp.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-multi-comp.d.ts","sourceRoot":"","sources":["no-multi-comp.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-multi-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview Prevent multiple component definition per file
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  onlyOneComponent: 'Declare only one React component per file',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow multiple component definition per file',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('no-multi-comp'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreStateless: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const configuration = context.options[0] || {};
+    const ignoreStateless = configuration.ignoreStateless || false;
+
+    /**
+     * Checks if the component is ignored
+     * @param {Object} component The component being checked.
+     * @returns {boolean} True if the component is ignored, false if not.
+     */
+    function isIgnored(component) {
+      return (
+        ignoreStateless && (
+          /Function/.test(component.node.type)
+          || utils.isPragmaComponentWrapper(component.node)
+        )
+      );
+    }
+
+    return {
+      'Program:exit'() {
+        if (components.length() <= 1) {
+          return;
+        }
+
+        values(components.list())
+          .filter((component) => !isIgnored(component))
+          .slice(1)
+          .forEach((component) => {
+            report(context, messages.onlyOneComponent, 'onlyOneComponent', {
+              node: component.node,
+            });
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-namespace.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-namespace.d.ts","sourceRoot":"","sources":["no-namespace.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-namespace.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Enforce that namespaces are not used in React elements
+ * @author Yacine Hmito
+ */
+
+'use strict';
+
+const elementType = require('jsx-ast-utils/elementType');
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noNamespace: 'React component {{name}} must not be in a namespace, as React does not support them',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce that namespaces are not used in React elements',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-namespace'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (isCreateElement(context, node) && node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
+          const name = node.arguments[0].value;
+          if (typeof name !== 'string' || name.indexOf(':') === -1) return undefined;
+          report(context, messages.noNamespace, 'noNamespace', {
+            node,
+            data: {
+              name,
+            },
+          });
+        }
+      },
+      JSXOpeningElement(node) {
+        const name = elementType(node);
+        if (typeof name !== 'string' || name.indexOf(':') === -1) return undefined;
+        report(context, messages.noNamespace, 'noNamespace', {
+          node,
+          data: {
+            name,
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-object-type-as-default-prop.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-object-type-as-default-prop.d.ts","sourceRoot":"","sources":["no-object-type-as-default-prop.js"],"names":[],"mappings":"wBAiFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-object-type-as-default-prop.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,105 @@
+/**
+ * @fileoverview Prevent usage of referential-type variables as default param in functional component
+ * @author Chang Yan
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const report = require('../util/report');
+
+const FORBIDDEN_TYPES_MAP = {
+  ArrowFunctionExpression: 'arrow function',
+  FunctionExpression: 'function expression',
+  ObjectExpression: 'object literal',
+  ArrayExpression: 'array literal',
+  ClassExpression: 'class expression',
+  NewExpression: 'construction expression',
+  JSXElement: 'JSX element',
+};
+
+const FORBIDDEN_TYPES = new Set(Object.keys(FORBIDDEN_TYPES_MAP));
+const MESSAGE_ID = 'forbiddenTypeDefaultParam';
+
+const messages = {
+  [MESSAGE_ID]: '{{propName}} has a/an {{forbiddenType}} as default prop. This could lead to potential infinite render loop in React. Use a variable reference instead of {{forbiddenType}}.',
+};
+function hasUsedObjectDestructuringSyntax(params) {
+  return (
+    params != null
+    && params.length >= 1
+    && params[0].type === 'ObjectPattern'
+  );
+}
+
+function verifyDefaultPropsDestructuring(context, properties) {
+  // Loop through each of the default params
+  properties.filter((prop) => prop.type === 'Property' && prop.value.type === 'AssignmentPattern').forEach((prop) => {
+    const propName = prop.key.name;
+    const propDefaultValue = prop.value;
+
+    const propDefaultValueType = propDefaultValue.right.type;
+
+    if (
+      propDefaultValueType === 'Literal'
+      && propDefaultValue.right.regex != null
+    ) {
+      report(context, messages[MESSAGE_ID], MESSAGE_ID, {
+        node: propDefaultValue,
+        data: {
+          propName,
+          forbiddenType: 'regex literal',
+        },
+      });
+    } else if (
+      astUtil.isCallExpression(propDefaultValue.right)
+      && propDefaultValue.right.callee.type === 'Identifier'
+      && propDefaultValue.right.callee.name === 'Symbol'
+    ) {
+      report(context, messages[MESSAGE_ID], MESSAGE_ID, {
+        node: propDefaultValue,
+        data: {
+          propName,
+          forbiddenType: 'Symbol literal',
+        },
+      });
+    } else if (FORBIDDEN_TYPES.has(propDefaultValueType)) {
+      report(context, messages[MESSAGE_ID], MESSAGE_ID, {
+        node: propDefaultValue,
+        data: {
+          propName,
+          forbiddenType: FORBIDDEN_TYPES_MAP[propDefaultValueType],
+        },
+      });
+    }
+  });
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of referential-type variables as default param in functional component',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-object-type-as-default-prop'),
+    },
+    messages,
+  },
+  create: Components.detect((context, components) => ({
+    'Program:exit'() {
+      const list = components.list();
+      values(list)
+        .filter((component) => hasUsedObjectDestructuringSyntax(component.node.params))
+        .forEach((component) => {
+          const node = component.node;
+          const properties = node.params[0].properties;
+          verifyDefaultPropsDestructuring(context, properties);
+        });
+    },
+  })),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-redundant-should-component-update.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-redundant-should-component-update.d.ts","sourceRoot":"","sources":["no-redundant-should-component-update.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-redundant-should-component-update.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Flag shouldComponentUpdate when extending PureComponent
+ */
+
+'use strict';
+
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noShouldCompUpdate: '{{component}} does not need shouldComponentUpdate when extending React.PureComponent.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of shouldComponentUpdate when extending React.PureComponent',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-redundant-should-component-update'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    /**
+     * Checks for shouldComponentUpdate property
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} Whether or not the property exists.
+     */
+    function hasShouldComponentUpdate(node) {
+      const properties = astUtil.getComponentProperties(node);
+      return properties.some((property) => {
+        const name = astUtil.getPropertyName(property);
+        return name === 'shouldComponentUpdate';
+      });
+    }
+
+    /**
+     * Get name of node if available
+     * @param {ASTNode} node The AST node being checked.
+     * @return {string} The name of the node
+     */
+    function getNodeName(node) {
+      if (node.id) {
+        return node.id.name;
+      }
+      if (node.parent && node.parent.id) {
+        return node.parent.id.name;
+      }
+      return '';
+    }
+
+    /**
+     * Checks for violation of rule
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function checkForViolation(node) {
+      if (componentUtil.isPureComponent(node, context)) {
+        const hasScu = hasShouldComponentUpdate(node);
+        if (hasScu) {
+          const className = getNodeName(node);
+          report(context, messages.noShouldCompUpdate, 'noShouldCompUpdate', {
+            node,
+            data: {
+              component: className,
+            },
+          });
+        }
+      }
+    }
+
+    return {
+      ClassDeclaration: checkForViolation,
+      ClassExpression: checkForViolation,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-render-return-value.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-render-return-value.d.ts","sourceRoot":"","sources":["no-render-return-value.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-render-return-value.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,82 @@
+/**
+ * @fileoverview Prevent usage of the return value of React.render
+ * @author Dustan Kasten
+ */
+
+'use strict';
+
+const testReactVersion = require('../util/version').testReactVersion;
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noReturnValue: 'Do not depend on the return value from {{node}}.render',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of the return value of ReactDOM.render',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-render-return-value'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    let calleeObjectName = /^ReactDOM$/;
+    if (testReactVersion(context, '>= 15.0.0')) {
+      calleeObjectName = /^ReactDOM$/;
+    } else if (testReactVersion(context, '^0.14.0')) {
+      calleeObjectName = /^React(DOM)?$/;
+    } else if (testReactVersion(context, '^0.13.0')) {
+      calleeObjectName = /^React$/;
+    }
+
+    return {
+      CallExpression(node) {
+        const callee = node.callee;
+        const parent = node.parent;
+        if (callee.type !== 'MemberExpression') {
+          return;
+        }
+
+        if (
+          callee.object.type !== 'Identifier'
+          || !calleeObjectName.test(callee.object.name)
+          || (!('name' in callee.property) || callee.property.name !== 'render')
+        ) {
+          return;
+        }
+
+        if (
+          parent.type === 'VariableDeclarator'
+          || parent.type === 'Property'
+          || parent.type === 'ReturnStatement'
+          || parent.type === 'ArrowFunctionExpression'
+          || parent.type === 'AssignmentExpression'
+        ) {
+          report(context, messages.noReturnValue, 'noReturnValue', {
+            node: callee,
+            data: {
+              node: callee.object.name,
+            },
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-set-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-set-state.d.ts","sourceRoot":"","sources":["no-set-state.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Prevent usage of setState
+ * @author Mark Dalgleish
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noSetState: 'Do not use setState',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of setState',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('no-set-state'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    /**
+     * Checks if the component is valid
+     * @param {Object} component The component to process
+     * @returns {boolean} True if the component is valid, false if not.
+     */
+    function isValid(component) {
+      return !!component && !component.useSetState;
+    }
+
+    /**
+     * Reports usages of setState for a given component
+     * @param {Object} component The component to process
+     */
+    function reportSetStateUsages(component) {
+      for (let i = 0, j = component.setStateUsages.length; i < j; i++) {
+        const setStateUsage = component.setStateUsages[i];
+        report(context, messages.noSetState, 'noSetState', {
+          node: setStateUsage,
+        });
+      }
+    }
+
+    return {
+      CallExpression(node) {
+        const callee = node.callee;
+        if (
+          callee.type !== 'MemberExpression'
+          || callee.object.type !== 'ThisExpression'
+          || callee.property.name !== 'setState'
+        ) {
+          return;
+        }
+        const component = components.get(utils.getParentComponent(node));
+        const setStateUsages = (component && component.setStateUsages) || [];
+        setStateUsages.push(callee);
+        components.set(node, {
+          useSetState: true,
+          setStateUsages,
+        });
+      },
+
+      'Program:exit'() {
+        values(components.list())
+          .filter((component) => !isValid(component))
+          .forEach((component) => {
+            reportSetStateUsages(component);
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-string-refs.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-string-refs.d.ts","sourceRoot":"","sources":["no-string-refs.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-string-refs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Prevent string definitions for references and prevent referencing this.refs
+ * @author Tom Hastjarjanto
+ */
+
+'use strict';
+
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const testReactVersion = require('../util/version').testReactVersion;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  thisRefsDeprecated: 'Using this.refs is deprecated.',
+  stringInRefDeprecated: 'Using string literals in ref attributes is deprecated.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow using string references',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('no-string-refs'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        noTemplateLiterals: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const checkRefsUsage = testReactVersion(context, '< 18.3.0'); // `this.refs` is writable in React 18.3.0 and later, see https://github.com/facebook/react/pull/28867
+    const detectTemplateLiterals = context.options[0] ? context.options[0].noTemplateLiterals : false;
+    /**
+     * Checks if we are using refs
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if we are using refs, false if not.
+     */
+    function isRefsUsage(node) {
+      return !!(
+        (componentUtil.getParentES6Component(context, node) || componentUtil.getParentES5Component(context, node))
+        && node.object.type === 'ThisExpression'
+        && node.property.name === 'refs'
+      );
+    }
+
+    /**
+     * Checks if we are using a ref attribute
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if we are using a ref attribute, false if not.
+     */
+    function isRefAttribute(node) {
+      return node.type === 'JSXAttribute'
+        && !!node.name
+        && node.name.name === 'ref';
+    }
+
+    /**
+     * Checks if a node contains a string value
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if the node contains a string value, false if not.
+     */
+    function containsStringLiteral(node) {
+      return !!node.value
+        && node.value.type === 'Literal'
+        && typeof node.value.value === 'string';
+    }
+
+    /**
+     * Checks if a node contains a string value within a jsx expression
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if the node contains a string value within a jsx expression, false if not.
+     */
+    function containsStringExpressionContainer(node) {
+      return !!node.value
+        && node.value.type === 'JSXExpressionContainer'
+        && node.value.expression
+        && ((node.value.expression.type === 'Literal' && typeof node.value.expression.value === 'string')
+        || (node.value.expression.type === 'TemplateLiteral' && detectTemplateLiterals));
+    }
+
+    return {
+      MemberExpression(node) {
+        if (checkRefsUsage && isRefsUsage(node)) {
+          report(context, messages.thisRefsDeprecated, 'thisRefsDeprecated', {
+            node,
+          });
+        }
+      },
+
+      JSXAttribute(node) {
+        if (
+          isRefAttribute(node)
+          && (containsStringLiteral(node) || containsStringExpressionContainer(node))
+        ) {
+          report(context, messages.stringInRefDeprecated, 'stringInRefDeprecated', {
+            node,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-this-in-sfc.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-this-in-sfc.d.ts","sourceRoot":"","sources":["no-this-in-sfc.js"],"names":[],"mappings":"wBAkBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-this-in-sfc.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Report "this" being used in stateless functional components.
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noThisInSFC: 'Stateless functional components should not use `this`',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow `this` from being used in stateless functional components',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-this-in-sfc'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create: Components.detect((context, components, utils) => ({
+    MemberExpression(node) {
+      if (node.object.type === 'ThisExpression') {
+        const component = components.get(utils.getParentStatelessComponent(node));
+        if (!component || (component.node && component.node.parent && component.node.parent.type === 'Property')) {
+          return;
+        }
+        report(context, messages.noThisInSFC, 'noThisInSFC', {
+          node,
+        });
+      }
+    },
+  })),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-typos.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-typos.d.ts","sourceRoot":"","sources":["no-typos.js"],"names":[],"mappings":"wBA+BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-typos.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,260 @@
+/**
+ * @fileoverview Prevent common casing typos
+ */
+
+'use strict';
+
+const PROP_TYPES = Object.keys(require('prop-types'));
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const report = require('../util/report');
+const lifecycleMethods = require('../util/lifecycleMethods');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const STATIC_CLASS_PROPERTIES = ['propTypes', 'contextTypes', 'childContextTypes', 'defaultProps'];
+
+const messages = {
+  typoPropTypeChain: 'Typo in prop type chain qualifier: {{name}}',
+  typoPropType: 'Typo in declared prop type: {{name}}',
+  typoStaticClassProp: 'Typo in static class property declaration',
+  typoPropDeclaration: 'Typo in property declaration',
+  typoLifecycleMethod: 'Typo in component lifecycle method declaration: {{actual}} should be {{expected}}',
+  staticLifecycleMethod: 'Lifecycle method should be static: {{method}}',
+  noPropTypesBinding: '`\'prop-types\'` imported without a local `PropTypes` binding.',
+  noReactBinding: '`\'react\'` imported without a local `React` binding.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow common typos',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('no-typos'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    let propTypesPackageName = null;
+    let reactPackageName = null;
+
+    function checkValidPropTypeQualifier(node) {
+      if (node.name !== 'isRequired') {
+        report(context, messages.typoPropTypeChain, 'typoPropTypeChain', {
+          node,
+          data: { name: node.name },
+        });
+      }
+    }
+
+    function checkValidPropType(node) {
+      if (node.name && !PROP_TYPES.some((propTypeName) => propTypeName === node.name)) {
+        report(context, messages.typoPropType, 'typoPropType', {
+          node,
+          data: { name: node.name },
+        });
+      }
+    }
+
+    function isPropTypesPackage(node) {
+      return (
+        node.type === 'Identifier'
+        && node.name === propTypesPackageName
+      ) || (
+        node.type === 'MemberExpression'
+        && node.property.name === 'PropTypes'
+        && node.object.name === reactPackageName
+      );
+    }
+
+    /* eslint-disable no-use-before-define */
+
+    function checkValidCallExpression(node) {
+      const callee = node.callee;
+      if (callee.type === 'MemberExpression' && callee.property.name === 'shape') {
+        checkValidPropObject(node.arguments[0]);
+      } else if (callee.type === 'MemberExpression' && callee.property.name === 'oneOfType') {
+        const args = node.arguments[0];
+        if (args && args.type === 'ArrayExpression') {
+          args.elements.forEach((el) => {
+            checkValidProp(el);
+          });
+        }
+      }
+    }
+
+    function checkValidProp(node) {
+      if ((!propTypesPackageName && !reactPackageName) || !node) {
+        return;
+      }
+
+      if (node.type === 'MemberExpression') {
+        if (
+          node.object.type === 'MemberExpression'
+          && isPropTypesPackage(node.object.object)
+        ) { // PropTypes.myProp.isRequired
+          checkValidPropType(node.object.property);
+          checkValidPropTypeQualifier(node.property);
+        } else if (
+          isPropTypesPackage(node.object)
+          && node.property.name !== 'isRequired'
+        ) { // PropTypes.myProp
+          checkValidPropType(node.property);
+        } else if (astUtil.isCallExpression(node.object)) {
+          checkValidPropTypeQualifier(node.property);
+          checkValidCallExpression(node.object);
+        }
+      } else if (astUtil.isCallExpression(node)) {
+        checkValidCallExpression(node);
+      }
+    }
+
+    /* eslint-enable no-use-before-define */
+
+    function checkValidPropObject(node) {
+      if (node && node.type === 'ObjectExpression') {
+        node.properties.forEach((prop) => checkValidProp(prop.value));
+      }
+    }
+
+    function reportErrorIfPropertyCasingTypo(propertyValue, propertyKey, isClassProperty) {
+      const propertyName = propertyKey.name;
+      if (propertyName === 'propTypes' || propertyName === 'contextTypes' || propertyName === 'childContextTypes') {
+        checkValidPropObject(propertyValue);
+      }
+      STATIC_CLASS_PROPERTIES.forEach((CLASS_PROP) => {
+        if (propertyName && CLASS_PROP.toLowerCase() === propertyName.toLowerCase() && CLASS_PROP !== propertyName) {
+          const messageId = isClassProperty
+            ? 'typoStaticClassProp'
+            : 'typoPropDeclaration';
+          report(context, messages[messageId], messageId, {
+            node: propertyKey,
+          });
+        }
+      });
+    }
+
+    function reportErrorIfLifecycleMethodCasingTypo(node) {
+      const key = node.key;
+      let nodeKeyName = key.name;
+      if (key.type === 'Literal') {
+        nodeKeyName = key.value;
+      }
+      if (key.type === 'PrivateName' || (node.computed && typeof nodeKeyName !== 'string')) {
+        return;
+      }
+
+      lifecycleMethods.static.forEach((method) => {
+        if (!node.static && nodeKeyName && nodeKeyName.toLowerCase() === method.toLowerCase()) {
+          report(context, messages.staticLifecycleMethod, 'staticLifecycleMethod', {
+            node,
+            data: {
+              method: nodeKeyName,
+            },
+          });
+        }
+      });
+
+      lifecycleMethods.instance.concat(lifecycleMethods.static).forEach((method) => {
+        if (nodeKeyName && method.toLowerCase() === nodeKeyName.toLowerCase() && method !== nodeKeyName) {
+          report(context, messages.typoLifecycleMethod, 'typoLifecycleMethod', {
+            node,
+            data: { actual: nodeKeyName, expected: method },
+          });
+        }
+      });
+    }
+
+    return {
+      ImportDeclaration(node) {
+        if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types"
+          if (node.specifiers.length > 0) {
+            propTypesPackageName = node.specifiers[0].local.name;
+          } else {
+            report(context, messages.noPropTypesBinding, 'noPropTypesBinding', {
+              node,
+            });
+          }
+        } else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
+          if (node.specifiers.length > 0) {
+            reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"`
+          } else {
+            report(context, messages.noReactBinding, 'noReactBinding', {
+              node,
+            });
+          }
+          if (node.specifiers.length >= 1) {
+            const propTypesSpecifier = node.specifiers.find((specifier) => (
+              specifier.imported
+              && specifier.imported.name === 'PropTypes'
+            ));
+            if (propTypesSpecifier) {
+              propTypesPackageName = propTypesSpecifier.local.name;
+            }
+          }
+        }
+      },
+
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!node.static || !componentUtil.isES6Component(node.parent.parent, context)) {
+          return;
+        }
+
+        reportErrorIfPropertyCasingTypo(node.value, node.key, true);
+      },
+
+      MemberExpression(node) {
+        const propertyName = node.property.name;
+
+        if (
+          !propertyName
+          || STATIC_CLASS_PROPERTIES.map((prop) => prop.toLocaleLowerCase()).indexOf(propertyName.toLowerCase()) === -1
+        ) {
+          return;
+        }
+
+        const relatedComponent = utils.getRelatedComponent(node);
+
+        if (
+          relatedComponent
+            && (componentUtil.isES6Component(relatedComponent.node, context) || (
+              relatedComponent.node.type !== 'ClassDeclaration' && utils.isReturningJSX(relatedComponent.node)))
+            && (node.parent && node.parent.type === 'AssignmentExpression' && node.parent.right)
+        ) {
+          reportErrorIfPropertyCasingTypo(node.parent.right, node.property, true);
+        }
+      },
+
+      MethodDefinition(node) {
+        if (!componentUtil.isES6Component(node.parent.parent, context)) {
+          return;
+        }
+
+        reportErrorIfLifecycleMethodCasingTypo(node);
+      },
+
+      ObjectExpression(node) {
+        const component = componentUtil.isES5Component(node, context) && components.get(node);
+
+        if (!component) {
+          return;
+        }
+
+        node.properties.filter((property) => property.type !== 'SpreadElement').forEach((property) => {
+          reportErrorIfPropertyCasingTypo(property.value, property.key, false);
+          reportErrorIfLifecycleMethodCasingTypo(property);
+        });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unescaped-entities.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unescaped-entities.d.ts","sourceRoot":"","sources":["no-unescaped-entities.js"],"names":[],"mappings":"wBAwCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unescaped-entities.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,157 @@
+/**
+ * @fileoverview HTML special characters should be escaped.
+ * @author Patrick Hayes
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const getSourceCode = require('../util/eslint').getSourceCode;
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+const getMessageData = require('../util/message');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+// NOTE: '<' and '{' are also problematic characters, but they do not need
+// to be included here because it is a syntax error when these characters are
+// included accidentally.
+const DEFAULTS = [{
+  char: '>',
+  alternatives: ['&gt;'],
+}, {
+  char: '"',
+  alternatives: ['&quot;', '&ldquo;', '&#34;', '&rdquo;'],
+}, {
+  char: '\'',
+  alternatives: ['&apos;', '&lsquo;', '&#39;', '&rsquo;'],
+}, {
+  char: '}',
+  alternatives: ['&#125;'],
+}];
+
+const messages = {
+  unescapedEntity: 'HTML entity, `{{entity}}` , must be escaped.',
+  unescapedEntityAlts: '`{{entity}}` can be escaped with {{alts}}.',
+  replaceWithAlt: 'Replace with `{{alt}}`.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    hasSuggestions: true,
+    docs: {
+      description: 'Disallow unescaped HTML entities from appearing in markup',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('no-unescaped-entities'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbid: {
+          type: 'array',
+          items: {
+            anyOf: [{
+              type: 'string',
+            }, {
+              type: 'object',
+              properties: {
+                char: {
+                  type: 'string',
+                },
+                alternatives: {
+                  type: 'array',
+                  uniqueItems: true,
+                  items: {
+                    type: 'string',
+                  },
+                },
+              },
+            }],
+          },
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    function reportInvalidEntity(node) {
+      const configuration = context.options[0] || {};
+      const entities = configuration.forbid || DEFAULTS;
+
+      // HTML entities are already escaped in node.value (as well as node.raw),
+      // so pull the raw text from getSourceCode(context)
+      for (let i = node.loc.start.line; i <= node.loc.end.line; i++) {
+        let rawLine = getSourceCode(context).lines[i - 1];
+        let start = 0;
+        let end = rawLine.length;
+        if (i === node.loc.start.line) {
+          start = node.loc.start.column;
+        }
+        if (i === node.loc.end.line) {
+          end = node.loc.end.column;
+        }
+        rawLine = rawLine.slice(start, end);
+        for (let j = 0; j < entities.length; j++) {
+          for (let index = 0; index < rawLine.length; index++) {
+            const c = rawLine[index];
+            if (typeof entities[j] === 'string') {
+              if (c === entities[j]) {
+                report(context, messages.unescapedEntity, 'unescapedEntity', {
+                  node,
+                  loc: { line: i, column: start + index },
+                  data: {
+                    entity: entities[j],
+                  },
+                });
+              }
+            } else if (c === entities[j].char) {
+              report(context, messages.unescapedEntityAlts, 'unescapedEntityAlts', {
+                node,
+                loc: { line: i, column: start + index },
+                data: {
+                  entity: entities[j].char,
+                  alts: entities[j].alternatives.map((alt) => `\`${alt}\``).join(', '),
+                },
+                suggest: entities[j].alternatives.map((alt) => Object.assign(
+                  getMessageData('replaceWithAlt', messages.replaceWithAlt),
+                  {
+                    data: { alt },
+                    fix(fixer) {
+                      const lineToChange = i - node.loc.start.line;
+
+                      const newText = node.raw.split('\n').map((line, idx) => {
+                        if (idx === lineToChange) {
+                          return line.slice(0, index) + alt + line.slice(index + 1);
+                        }
+
+                        return line;
+                      }).join('\n');
+
+                      return fixer.replaceText(node, newText);
+                    },
+                  }
+                )),
+              });
+            }
+          }
+        }
+      }
+    }
+
+    return {
+      'Literal, JSXText'(node) {
+        if (jsxUtil.isJSX(node.parent)) {
+          reportInvalidEntity(node);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unknown-property.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unknown-property.d.ts","sourceRoot":"","sources":["no-unknown-property.js"],"names":[],"mappings":"wBAshBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unknown-property.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,671 @@
+/**
+ * @fileoverview Prevent usage of unknown DOM property
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const has = require('hasown');
+const docsUrl = require('../util/docsUrl');
+const getText = require('../util/eslint').getText;
+const testReactVersion = require('../util/version').testReactVersion;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const DEFAULTS = {
+  ignore: [],
+  requireDataLowercase: false,
+};
+
+const DOM_ATTRIBUTE_NAMES = {
+  'accept-charset': 'acceptCharset',
+  class: 'className',
+  'http-equiv': 'httpEquiv',
+  crossorigin: 'crossOrigin',
+  for: 'htmlFor',
+  nomodule: 'noModule',
+};
+
+const ATTRIBUTE_TAGS_MAP = {
+  abbr: ['th', 'td'],
+  charset: ['meta'],
+  checked: ['input'],
+  // image is required for SVG support, all other tags are HTML.
+  crossOrigin: ['script', 'img', 'video', 'audio', 'link', 'image'],
+  displaystyle: ['math'],
+  // https://html.spec.whatwg.org/multipage/links.html#downloading-resources
+  download: ['a', 'area'],
+  fill: [ // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
+    // Fill color
+    'altGlyph',
+    'circle',
+    'ellipse',
+    'g',
+    'line',
+    'marker',
+    'mask',
+    'path',
+    'polygon',
+    'polyline',
+    'rect',
+    'svg',
+    'symbol',
+    'text',
+    'textPath',
+    'tref',
+    'tspan',
+    'use',
+    // Animation final state
+    'animate',
+    'animateColor',
+    'animateMotion',
+    'animateTransform',
+    'set',
+  ],
+  focusable: ['svg'],
+  imageSizes: ['link'],
+  imageSrcSet: ['link'],
+  property: ['meta'],
+  viewBox: ['marker', 'pattern', 'svg', 'symbol', 'view'],
+  as: ['link'],
+  align: ['applet', 'caption', 'col', 'colgroup', 'hr', 'iframe', 'img', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'], // deprecated, but known
+  valign: ['tr', 'td', 'th', 'thead', 'tbody', 'tfoot', 'colgroup', 'col'], // deprecated, but known
+  noModule: ['script'],
+  // Media events allowed only on audio and video tags, see https://github.com/facebook/react/blob/256aefbea1449869620fb26f6ec695536ab453f5/CHANGELOG.md#notable-enhancements
+  onAbort: ['audio', 'video'],
+  onCancel: ['dialog'],
+  onCanPlay: ['audio', 'video'],
+  onCanPlayThrough: ['audio', 'video'],
+  onClose: ['dialog'],
+  onDurationChange: ['audio', 'video'],
+  onEmptied: ['audio', 'video'],
+  onEncrypted: ['audio', 'video'],
+  onEnded: ['audio', 'video'],
+  onError: ['audio', 'video', 'img', 'link', 'source', 'script', 'picture', 'iframe'],
+  onLoad: ['script', 'img', 'link', 'picture', 'iframe', 'object', 'source'],
+  onLoadedData: ['audio', 'video'],
+  onLoadedMetadata: ['audio', 'video'],
+  onLoadStart: ['audio', 'video'],
+  onPause: ['audio', 'video'],
+  onPlay: ['audio', 'video'],
+  onPlaying: ['audio', 'video'],
+  onProgress: ['audio', 'video'],
+  onRateChange: ['audio', 'video'],
+  onResize: ['audio', 'video'],
+  onSeeked: ['audio', 'video'],
+  onSeeking: ['audio', 'video'],
+  onStalled: ['audio', 'video'],
+  onSuspend: ['audio', 'video'],
+  onTimeUpdate: ['audio', 'video'],
+  onVolumeChange: ['audio', 'video'],
+  onWaiting: ['audio', 'video'],
+  autoPictureInPicture: ['video'],
+  controls: ['audio', 'video'],
+  controlsList: ['audio', 'video'],
+  disablePictureInPicture: ['video'],
+  disableRemotePlayback: ['audio', 'video'],
+  loop: ['audio', 'video'],
+  muted: ['audio', 'video'],
+  playsInline: ['video'],
+  allowFullScreen: ['iframe', 'video'],
+  webkitAllowFullScreen: ['iframe', 'video'],
+  mozAllowFullScreen: ['iframe', 'video'],
+  poster: ['video'],
+  preload: ['audio', 'video'],
+  scrolling: ['iframe'],
+  returnValue: ['dialog'],
+  webkitDirectory: ['input'],
+  shadowrootmode: ['template'],
+  shadowrootclonable: ['template'],
+  shadowrootdelegatesfocus: ['template'],
+  shadowrootserializable: ['template'],
+  'transform-origin': ['rect'],
+};
+
+const SVGDOM_ATTRIBUTE_NAMES = {
+  'accent-height': 'accentHeight',
+  'alignment-baseline': 'alignmentBaseline',
+  'arabic-form': 'arabicForm',
+  'baseline-shift': 'baselineShift',
+  'cap-height': 'capHeight',
+  'clip-path': 'clipPath',
+  'clip-rule': 'clipRule',
+  'color-interpolation': 'colorInterpolation',
+  'color-interpolation-filters': 'colorInterpolationFilters',
+  'color-profile': 'colorProfile',
+  'color-rendering': 'colorRendering',
+  'dominant-baseline': 'dominantBaseline',
+  'enable-background': 'enableBackground',
+  'fill-opacity': 'fillOpacity',
+  'fill-rule': 'fillRule',
+  'flood-color': 'floodColor',
+  'flood-opacity': 'floodOpacity',
+  'font-family': 'fontFamily',
+  'font-size': 'fontSize',
+  'font-size-adjust': 'fontSizeAdjust',
+  'font-stretch': 'fontStretch',
+  'font-style': 'fontStyle',
+  'font-variant': 'fontVariant',
+  'font-weight': 'fontWeight',
+  'glyph-name': 'glyphName',
+  'glyph-orientation-horizontal': 'glyphOrientationHorizontal',
+  'glyph-orientation-vertical': 'glyphOrientationVertical',
+  'horiz-adv-x': 'horizAdvX',
+  'horiz-origin-x': 'horizOriginX',
+  'image-rendering': 'imageRendering',
+  'letter-spacing': 'letterSpacing',
+  'lighting-color': 'lightingColor',
+  'marker-end': 'markerEnd',
+  'marker-mid': 'markerMid',
+  'marker-start': 'markerStart',
+  'overline-position': 'overlinePosition',
+  'overline-thickness': 'overlineThickness',
+  'paint-order': 'paintOrder',
+  'panose-1': 'panose1',
+  'pointer-events': 'pointerEvents',
+  'rendering-intent': 'renderingIntent',
+  'shape-rendering': 'shapeRendering',
+  'stop-color': 'stopColor',
+  'stop-opacity': 'stopOpacity',
+  'strikethrough-position': 'strikethroughPosition',
+  'strikethrough-thickness': 'strikethroughThickness',
+  'stroke-dasharray': 'strokeDasharray',
+  'stroke-dashoffset': 'strokeDashoffset',
+  'stroke-linecap': 'strokeLinecap',
+  'stroke-linejoin': 'strokeLinejoin',
+  'stroke-miterlimit': 'strokeMiterlimit',
+  'stroke-opacity': 'strokeOpacity',
+  'stroke-width': 'strokeWidth',
+  'text-anchor': 'textAnchor',
+  'text-decoration': 'textDecoration',
+  'text-rendering': 'textRendering',
+  'underline-position': 'underlinePosition',
+  'underline-thickness': 'underlineThickness',
+  'unicode-bidi': 'unicodeBidi',
+  'unicode-range': 'unicodeRange',
+  'units-per-em': 'unitsPerEm',
+  'v-alphabetic': 'vAlphabetic',
+  'v-hanging': 'vHanging',
+  'v-ideographic': 'vIdeographic',
+  'v-mathematical': 'vMathematical',
+  'vector-effect': 'vectorEffect',
+  'vert-adv-y': 'vertAdvY',
+  'vert-origin-x': 'vertOriginX',
+  'vert-origin-y': 'vertOriginY',
+  'word-spacing': 'wordSpacing',
+  'writing-mode': 'writingMode',
+  'x-height': 'xHeight',
+  'xlink:actuate': 'xlinkActuate',
+  'xlink:arcrole': 'xlinkArcrole',
+  'xlink:href': 'xlinkHref',
+  'xlink:role': 'xlinkRole',
+  'xlink:show': 'xlinkShow',
+  'xlink:title': 'xlinkTitle',
+  'xlink:type': 'xlinkType',
+  'xml:base': 'xmlBase',
+  'xml:lang': 'xmlLang',
+  'xml:space': 'xmlSpace',
+};
+
+const DOM_PROPERTY_NAMES_ONE_WORD = [
+  // Global attributes - can be used on any HTML/DOM element
+  // See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
+  'dir', 'draggable', 'hidden', 'id', 'lang', 'nonce', 'part', 'slot', 'style', 'title', 'translate', 'inert',
+  // Element specific attributes
+  // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too)
+  // To be considered if these should be added also to ATTRIBUTE_TAGS_MAP
+  'accept', 'action', 'allow', 'alt', 'as', 'async', 'buffered', 'capture', 'challenge', 'cite', 'code', 'cols',
+  'content', 'coords', 'csp', 'data', 'decoding', 'default', 'defer', 'disabled', 'form',
+  'headers', 'height', 'high', 'href', 'icon', 'importance', 'integrity', 'kind', 'label',
+  'language', 'loading', 'list', 'loop', 'low', 'manifest', 'max', 'media', 'method', 'min', 'multiple', 'muted',
+  'name', 'open', 'optimum', 'pattern', 'ping', 'placeholder', 'poster', 'preload', 'profile',
+  'rel', 'required', 'reversed', 'role', 'rows', 'sandbox', 'scope', 'seamless', 'selected', 'shape', 'size', 'sizes',
+  'span', 'src', 'start', 'step', 'summary', 'target', 'type', 'value', 'width', 'wmode', 'wrap',
+  // SVG attributes
+  // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
+  'accumulate', 'additive', 'alphabetic', 'amplitude', 'ascent', 'azimuth', 'bbox', 'begin',
+  'bias', 'by', 'clip', 'color', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'direction',
+  'display', 'divisor', 'dur', 'dx', 'dy', 'elevation', 'end', 'exponent', 'fill', 'filter',
+  'format', 'from', 'fr', 'fx', 'fy', 'g1', 'g2', 'hanging', 'height', 'hreflang', 'ideographic',
+  'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'local', 'mask', 'mode',
+  'offset', 'opacity', 'operator', 'order', 'orient', 'orientation', 'origin', 'overflow', 'path',
+  'ping', 'points', 'r', 'radius', 'rel', 'restart', 'result', 'rotate', 'rx', 'ry', 'scale',
+  'seed', 'slope', 'spacing', 'speed', 'stemh', 'stemv', 'string', 'stroke', 'to', 'transform',
+  'u1', 'u2', 'unicode', 'values', 'version', 'visibility', 'widths', 'x', 'x1', 'x2', 'xmlns',
+  'y', 'y1', 'y2', 'z',
+  // OpenGraph meta tag attributes
+  'property',
+  // React specific attributes
+  'ref', 'key', 'children',
+  // Non-standard
+  'results', 'security',
+  // Video specific
+  'controls',
+  // popovers
+  'popover', 'popovertarget', 'popovertargetaction',
+];
+
+const DOM_PROPERTY_NAMES_TWO_WORDS = [
+  // Global attributes - can be used on any HTML/DOM element
+  // See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
+  'accessKey', 'autoCapitalize', 'autoFocus', 'contentEditable', 'enterKeyHint', 'exportParts',
+  'inputMode', 'itemID', 'itemRef', 'itemProp', 'itemScope', 'itemType', 'spellCheck', 'tabIndex',
+  // Element specific attributes
+  // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes (includes global attributes too)
+  // To be considered if these should be added also to ATTRIBUTE_TAGS_MAP
+  'acceptCharset', 'autoComplete', 'autoPlay', 'border', 'cellPadding', 'cellSpacing', 'classID', 'codeBase',
+  'colSpan', 'contextMenu', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
+  'frameBorder', 'hrefLang', 'httpEquiv', 'imageSizes', 'imageSrcSet', 'isMap', 'keyParams', 'keyType', 'marginHeight', 'marginWidth',
+  'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'onAnimationEnd', 'onAnimationIteration', 'onAnimationStart',
+  'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart',
+  'onCompositionUpdate', 'onCut', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave',
+  'onError', 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onLoad', 'onWheel', 'onDragOver',
+  'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver',
+  'onMouseUp', 'onPaste', 'onScroll', 'onSelect', 'onSubmit', 'onBeforeToggle', 'onToggle', 'onTransitionEnd', 'radioGroup',
+  'readOnly', 'referrerPolicy', 'rowSpan', 'srcDoc', 'srcLang', 'srcSet', 'useMap', 'fetchPriority',
+  // SVG attributes
+  // See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
+  'crossOrigin', 'accentHeight', 'alignmentBaseline', 'arabicForm', 'attributeName',
+  'attributeType', 'baseFrequency', 'baselineShift', 'baseProfile', 'calcMode', 'capHeight',
+  'clipPathUnits', 'clipPath', 'clipRule', 'colorInterpolation', 'colorInterpolationFilters',
+  'colorProfile', 'colorRendering', 'contentScriptType', 'contentStyleType', 'diffuseConstant',
+  'dominantBaseline', 'edgeMode', 'enableBackground', 'fillOpacity', 'fillRule', 'filterRes',
+  'filterUnits', 'floodColor', 'floodOpacity', 'fontFamily', 'fontSize', 'fontSizeAdjust',
+  'fontStretch', 'fontStyle', 'fontVariant', 'fontWeight', 'glyphName',
+  'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform',
+  'gradientUnits', 'horizAdvX', 'horizOriginX', 'imageRendering', 'kernelMatrix',
+  'kernelUnitLength', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing',
+  'lightingColor', 'limitingConeAngle', 'markerEnd', 'markerMid', 'markerStart', 'markerHeight',
+  'markerUnits', 'markerWidth', 'maskContentUnits', 'maskUnits', 'mathematical', 'numOctaves',
+  'overlinePosition', 'overlineThickness', 'panose1', 'paintOrder', 'pathLength',
+  'patternContentUnits', 'patternTransform', 'patternUnits', 'pointerEvents', 'pointsAtX',
+  'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits',
+  'referrerPolicy', 'refX', 'refY', 'rendering-intent', 'repeatCount', 'repeatDur',
+  'requiredExtensions', 'requiredFeatures', 'shapeRendering', 'specularConstant',
+  'specularExponent', 'spreadMethod', 'startOffset', 'stdDeviation', 'stitchTiles', 'stopColor',
+  'stopOpacity', 'strikethroughPosition', 'strikethroughThickness', 'strokeDasharray',
+  'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', 'strokeOpacity',
+  'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', 'targetY',
+  'textAnchor', 'textDecoration', 'textRendering', 'textLength', 'transformOrigin',
+  'underlinePosition', 'underlineThickness', 'unicodeBidi', 'unicodeRange', 'unitsPerEm',
+  'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', 'vectorEffect', 'vertAdvY',
+  'vertOriginX', 'vertOriginY', 'viewBox', 'viewTarget', 'wordSpacing', 'writingMode', 'xHeight',
+  'xChannelSelector', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', 'xlinkShow',
+  'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlnsXlink', 'xmlSpace', 'yChannelSelector',
+  'zoomAndPan',
+  // Safari/Apple specific, no listing available
+  'autoCorrect', // https://stackoverflow.com/questions/47985384/html-autocorrect-for-text-input-is-not-working
+  'autoSave', // https://stackoverflow.com/questions/25456396/what-is-autosave-attribute-supposed-to-do-how-do-i-use-it
+  // React specific attributes https://reactjs.org/docs/dom-elements.html#differences-in-attributes
+  'className', 'dangerouslySetInnerHTML', 'defaultValue', 'defaultChecked', 'htmlFor',
+  // Events' capture events
+  'onBeforeInput', 'onChange',
+  'onInvalid', 'onReset', 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'suppressContentEditableWarning', 'suppressHydrationWarning',
+  'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded',
+  'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onResize',
+  'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting',
+  'onCopyCapture', 'onCutCapture', 'onPasteCapture', 'onCompositionEndCapture', 'onCompositionStartCapture', 'onCompositionUpdateCapture',
+  'onFocusCapture', 'onBlurCapture', 'onChangeCapture', 'onBeforeInputCapture', 'onInputCapture', 'onResetCapture', 'onSubmitCapture',
+  'onInvalidCapture', 'onLoadCapture', 'onErrorCapture', 'onKeyDownCapture', 'onKeyPressCapture', 'onKeyUpCapture',
+  'onAbortCapture', 'onCanPlayCapture', 'onCanPlayThroughCapture', 'onDurationChangeCapture', 'onEmptiedCapture', 'onEncryptedCapture',
+  'onEndedCapture', 'onLoadedDataCapture', 'onLoadedMetadataCapture', 'onLoadStartCapture', 'onPauseCapture', 'onPlayCapture',
+  'onPlayingCapture', 'onProgressCapture', 'onRateChangeCapture', 'onSeekedCapture', 'onSeekingCapture', 'onStalledCapture', 'onSuspendCapture',
+  'onTimeUpdateCapture', 'onVolumeChangeCapture', 'onWaitingCapture', 'onSelectCapture', 'onTouchCancelCapture', 'onTouchEndCapture',
+  'onTouchMoveCapture', 'onTouchStartCapture', 'onScrollCapture', 'onWheelCapture', 'onAnimationEndCapture', 'onAnimationIteration',
+  'onAnimationStartCapture', 'onTransitionEndCapture',
+  'onAuxClick', 'onAuxClickCapture', 'onClickCapture', 'onContextMenuCapture', 'onDoubleClickCapture',
+  'onDragCapture', 'onDragEndCapture', 'onDragEnterCapture', 'onDragExitCapture', 'onDragLeaveCapture',
+  'onDragOverCapture', 'onDragStartCapture', 'onDropCapture', 'onMouseDown', 'onMouseDownCapture',
+  'onMouseMoveCapture', 'onMouseOutCapture', 'onMouseOverCapture', 'onMouseUpCapture',
+  // Video specific
+  'autoPictureInPicture', 'controlsList', 'disablePictureInPicture', 'disableRemotePlayback',
+  // popovers
+  'popoverTarget', 'popoverTargetAction',
+];
+
+const DOM_PROPERTIES_IGNORE_CASE = ['charset', 'allowFullScreen', 'webkitAllowFullScreen', 'mozAllowFullScreen', 'webkitDirectory', 'popoverTarget', 'popoverTargetAction'];
+
+const ARIA_PROPERTIES = [
+  // See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
+  // Global attributes
+  'aria-atomic', 'aria-braillelabel', 'aria-brailleroledescription', 'aria-busy', 'aria-controls', 'aria-current',
+  'aria-describedby', 'aria-description', 'aria-details',
+  'aria-disabled', 'aria-dropeffect', 'aria-errormessage', 'aria-flowto', 'aria-grabbed', 'aria-haspopup',
+  'aria-hidden', 'aria-invalid', 'aria-keyshortcuts', 'aria-label', 'aria-labelledby', 'aria-live',
+  'aria-owns', 'aria-relevant', 'aria-roledescription',
+  // Widget attributes
+  'aria-autocomplete', 'aria-checked', 'aria-expanded', 'aria-level', 'aria-modal', 'aria-multiline', 'aria-multiselectable',
+  'aria-orientation', 'aria-placeholder', 'aria-pressed', 'aria-readonly', 'aria-required', 'aria-selected',
+  'aria-sort', 'aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext',
+  // Relationship attributes
+  'aria-activedescendant', 'aria-colcount', 'aria-colindex', 'aria-colindextext', 'aria-colspan',
+  'aria-posinset', 'aria-rowcount', 'aria-rowindex', 'aria-rowindextext', 'aria-rowspan', 'aria-setsize',
+];
+
+const REACT_ON_PROPS = [
+  'onGotPointerCapture',
+  'onGotPointerCaptureCapture',
+  'onLostPointerCapture',
+  'onLostPointerCapture',
+  'onLostPointerCaptureCapture',
+  'onPointerCancel',
+  'onPointerCancelCapture',
+  'onPointerDown',
+  'onPointerDownCapture',
+  'onPointerEnter',
+  'onPointerEnterCapture',
+  'onPointerLeave',
+  'onPointerLeaveCapture',
+  'onPointerMove',
+  'onPointerMoveCapture',
+  'onPointerOut',
+  'onPointerOutCapture',
+  'onPointerOver',
+  'onPointerOverCapture',
+  'onPointerUp',
+  'onPointerUpCapture',
+];
+
+function getDOMPropertyNames(context) {
+  return [].concat(
+    DOM_PROPERTY_NAMES_TWO_WORDS,
+    DOM_PROPERTY_NAMES_ONE_WORD,
+
+    testReactVersion(context, '>= 16.1.0') ? [].concat(
+      testReactVersion(context, '>= 16.4.0') ? [].concat(
+        // these were added in React v16.4.0, see https://reactjs.org/blog/2018/05/23/react-v-16-4.html and https://github.com/facebook/react/pull/12507
+        REACT_ON_PROPS,
+        testReactVersion(context, '>= 19') ? [
+          // precedence was added in React v19, see https://react.dev/blog/2024/04/25/react-19#support-for-stylesheets
+          'precedence',
+        ] : []
+      ) : []
+    ) : [
+      // this was removed in React v16.1+, see https://github.com/facebook/react/pull/10823
+      'allowTransparency',
+    ]
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Checks if a node's parent is a JSX tag that is written with lowercase letters,
+ * and is not a custom web component. Custom web components have a hyphen in tag name,
+ * or have an `is="some-elem"` attribute.
+ *
+ * Note: does not check if a tag's parent against a list of standard HTML/DOM tags. For example,
+ * a `<fake>`'s child would return `true` because "fake" is written only with lowercase letters
+ * without a hyphen and does not have a `is="some-elem"` attribute.
+ *
+ * @param {Object} childNode - JSX element being tested.
+ * @returns {boolean} Whether or not the node name match the JSX tag convention.
+ */
+function isValidHTMLTagInJSX(childNode) {
+  const tagConvention = /^[a-z][^-]*$/;
+  if (tagConvention.test(childNode.parent.name.name)) {
+    return !childNode.parent.attributes.some((attrNode) => (
+      attrNode.type === 'JSXAttribute'
+        && attrNode.name.type === 'JSXIdentifier'
+        && attrNode.name.name === 'is'
+        // To learn more about custom web components and `is` attribute,
+        // see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example
+
+    ));
+  }
+  return false;
+}
+
+/**
+ * Checks if the attribute name is included in the attributes that are excluded
+ * from the camel casing.
+ *
+ * // returns 'charSet'
+ * @example normalizeAttributeCase('charset')
+ *
+ * Note - these exclusions are not made by React core team, but `eslint-plugin-react` community.
+ *
+ * @param {string} name - Attribute name to be normalized
+ * @returns {string} Result
+ */
+function normalizeAttributeCase(name) {
+  return DOM_PROPERTIES_IGNORE_CASE.find((element) => element.toLowerCase() === name.toLowerCase()) || name;
+}
+
+/**
+ * Checks if an attribute name is a valid `data-*` attribute:
+ * if the name starts with "data-" and has alphanumeric words (browsers require lowercase, but React and TS lowercase them),
+ * not start with any casing of "xml", and separated by hyphens (-) (which is also called "kebab case" or "dash case"),
+ * then the attribute is a valid data attribute.
+ *
+ * @param {string} name - Attribute name to be tested
+ * @returns {boolean} Result
+ */
+function isValidDataAttribute(name) {
+  return !/^data-xml/i.test(name) && /^data-[^:]*$/.test(name);
+}
+
+/**
+ * Checks if an attribute name has at least one uppercase characters
+ *
+ * @param {string} name
+ * @returns {boolean} Result
+ */
+function hasUpperCaseCharacter(name) {
+  return name.toLowerCase() !== name;
+}
+
+/**
+ * Checks if an attribute name is a standard aria attribute by compering it to a list
+ * of standard aria property names
+ *
+ * @param {string} name - Attribute name to be tested
+ * @returns {boolean} Result
+ */
+
+function isValidAriaAttribute(name) {
+  return ARIA_PROPERTIES.some((element) => element === name);
+}
+
+/**
+ * Extracts the tag name for the JSXAttribute
+ * @param {JSXAttribute} node - JSXAttribute being tested.
+ * @returns {string | null} tag name
+ */
+function getTagName(node) {
+  if (
+    node
+    && node.parent
+    && node.parent.name
+  ) {
+    return node.parent.name.name;
+  }
+  return null;
+}
+
+/**
+ * Test wether the tag name for the JSXAttribute is
+ * something like <Foo.bar />
+ * @param {JSXAttribute} node - JSXAttribute being tested.
+ * @returns {boolean} result
+ */
+function tagNameHasDot(node) {
+  return !!(
+    node.parent
+    && node.parent.name
+    && node.parent.name.type === 'JSXMemberExpression'
+  );
+}
+
+/**
+ * Get the standard name of the attribute.
+ * @param {string} name - Name of the attribute.
+ * @param {object} context - eslint context
+ * @returns {string | undefined} The standard name of the attribute, or undefined if no standard name was found.
+ */
+function getStandardName(name, context) {
+  if (has(DOM_ATTRIBUTE_NAMES, name)) {
+    return DOM_ATTRIBUTE_NAMES[/** @type {keyof DOM_ATTRIBUTE_NAMES} */ (name)];
+  }
+  if (has(SVGDOM_ATTRIBUTE_NAMES, name)) {
+    return SVGDOM_ATTRIBUTE_NAMES[/** @type {keyof SVGDOM_ATTRIBUTE_NAMES} */ (name)];
+  }
+  const names = getDOMPropertyNames(context);
+
+  // Let's find a possible attribute match with a case-insensitive search.
+  return names.find((element) => element.toLowerCase() === name.toLowerCase());
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  invalidPropOnTag: 'Invalid property \'{{name}}\' found on tag \'{{tagName}}\', but it is only allowed on: {{allowedTags}}',
+  unknownPropWithStandardName: 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead',
+  unknownProp: 'Unknown property \'{{name}}\' found',
+  dataLowercaseRequired: 'React does not recognize data-* props with uppercase characters on a DOM element. Found \'{{name}}\', use \'{{lowerCaseName}}\' instead',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of unknown DOM property',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('no-unknown-property'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignore: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        requireDataLowercase: {
+          type: 'boolean',
+          default: false,
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    function getIgnoreConfig() {
+      return (context.options[0] && context.options[0].ignore) || DEFAULTS.ignore;
+    }
+
+    function getRequireDataLowercase() {
+      return (context.options[0] && typeof context.options[0].requireDataLowercase !== 'undefined')
+        ? !!context.options[0].requireDataLowercase
+        : DEFAULTS.requireDataLowercase;
+    }
+
+    return {
+      JSXAttribute(node) {
+        const ignoreNames = getIgnoreConfig();
+        const actualName = getText(context, node.name);
+        if (ignoreNames.indexOf(actualName) >= 0) {
+          return;
+        }
+        const name = normalizeAttributeCase(actualName);
+
+        // Ignore tags like <Foo.bar />
+        if (tagNameHasDot(node)) {
+          return;
+        }
+
+        if (isValidDataAttribute(name)) {
+          if (getRequireDataLowercase() && hasUpperCaseCharacter(name)) {
+            report(context, messages.dataLowercaseRequired, 'dataLowercaseRequired', {
+              node,
+              data: {
+                name: actualName,
+                lowerCaseName: actualName.toLowerCase(),
+              },
+            });
+          }
+
+          return;
+        }
+
+        if (isValidAriaAttribute(name)) { return; }
+
+        const tagName = getTagName(node);
+
+        if (tagName === 'fbt' || tagName === 'fbs') { return; } // fbt/fbs nodes are bonkers, let's not go there
+
+        if (!isValidHTMLTagInJSX(node)) { return; }
+
+        // Let's dive deeper into tags that are HTML/DOM elements (`<button>`), and not React components (`<Button />`)
+
+        // Some attributes are allowed on some tags only
+        const allowedTags = has(ATTRIBUTE_TAGS_MAP, name)
+          ? ATTRIBUTE_TAGS_MAP[/** @type {keyof ATTRIBUTE_TAGS_MAP} */ (name)]
+          : null;
+        if (tagName && allowedTags) {
+          // Scenario 1A: Allowed attribute found where not supposed to, report it
+          if (allowedTags.indexOf(tagName) === -1) {
+            report(context, messages.invalidPropOnTag, 'invalidPropOnTag', {
+              node,
+              data: {
+                name: actualName,
+                tagName,
+                allowedTags: allowedTags.join(', '),
+              },
+            });
+          }
+          // Scenario 1B: There are allowed attributes on allowed tags, no need to report it
+          return;
+        }
+
+        // Let's see if the attribute is a close version to some standard property name
+        const standardName = getStandardName(name, context);
+
+        const hasStandardNameButIsNotUsed = standardName && standardName !== name;
+        const usesStandardName = standardName && standardName === name;
+
+        if (usesStandardName) {
+          // Scenario 2A: The attribute name is the standard name, no need to report it
+          return;
+        }
+
+        if (hasStandardNameButIsNotUsed) {
+          // Scenario 2B: The name of the attribute is close to a standard one, report it with the standard name
+          report(context, messages.unknownPropWithStandardName, 'unknownPropWithStandardName', {
+            node,
+            data: {
+              name: actualName,
+              standardName,
+            },
+            fix(fixer) {
+              return fixer.replaceText(node.name, standardName);
+            },
+          });
+          return;
+        }
+
+        // Scenario 3: We have an attribute that is unknown, report it
+        report(context, messages.unknownProp, 'unknownProp', {
+          node,
+          data: {
+            name: actualName,
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unsafe.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unsafe.d.ts","sourceRoot":"","sources":["no-unsafe.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unsafe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,151 @@
+/**
+ * @fileoverview Prevent usage of unsafe lifecycle methods
+ * @author Sergei Startsev
+ */
+
+'use strict';
+
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const testReactVersion = require('../util/version').testReactVersion;
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  unsafeMethod: '{{method}} is unsafe for use in async rendering. Update the component to use {{newMethod}} instead. {{details}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow usage of unsafe lifecycle methods',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-unsafe'),
+    },
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          checkAliases: {
+            default: false,
+            type: 'boolean',
+          },
+        },
+        additionalProperties: false,
+      },
+    ],
+  },
+
+  create(context) {
+    const config = context.options[0] || {};
+    const checkAliases = config.checkAliases || false;
+
+    const isApplicable = testReactVersion(context, '>= 16.3.0');
+    if (!isApplicable) {
+      return {};
+    }
+
+    const unsafe = {
+      UNSAFE_componentWillMount: {
+        newMethod: 'componentDidMount',
+        details: 'See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.',
+      },
+      UNSAFE_componentWillReceiveProps: {
+        newMethod: 'getDerivedStateFromProps',
+        details: 'See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.',
+      },
+      UNSAFE_componentWillUpdate: {
+        newMethod: 'componentDidUpdate',
+        details: 'See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.',
+      },
+    };
+    if (checkAliases) {
+      unsafe.componentWillMount = unsafe.UNSAFE_componentWillMount;
+      unsafe.componentWillReceiveProps = unsafe.UNSAFE_componentWillReceiveProps;
+      unsafe.componentWillUpdate = unsafe.UNSAFE_componentWillUpdate;
+    }
+
+    /**
+     * Returns a list of unsafe methods
+     * @returns {Array} A list of unsafe methods
+     */
+    function getUnsafeMethods() {
+      return Object.keys(unsafe);
+    }
+
+    /**
+     * Checks if a passed method is unsafe
+     * @param {string} method Life cycle method
+     * @returns {boolean} Returns true for unsafe methods, otherwise returns false
+     */
+    function isUnsafe(method) {
+      const unsafeMethods = getUnsafeMethods();
+      return unsafeMethods.indexOf(method) !== -1;
+    }
+
+    /**
+     * Reports the error for an unsafe method
+     * @param {ASTNode} node The AST node being checked
+     * @param {string} method Life cycle method
+     */
+    function checkUnsafe(node, method) {
+      if (!isUnsafe(method)) {
+        return;
+      }
+
+      const meta = unsafe[method];
+      const newMethod = meta.newMethod;
+      const details = meta.details;
+
+      const propertyNode = astUtil.getComponentProperties(node)
+        .find((property) => astUtil.getPropertyName(property) === method);
+
+      report(context, messages.unsafeMethod, 'unsafeMethod', {
+        node: propertyNode,
+        data: {
+          method,
+          newMethod,
+          details,
+        },
+      });
+    }
+
+    /**
+     * Returns life cycle methods if available
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {Array} The array of methods.
+     */
+    function getLifeCycleMethods(node) {
+      const properties = astUtil.getComponentProperties(node);
+      return properties.map((property) => astUtil.getPropertyName(property));
+    }
+
+    /**
+     * Checks life cycle methods
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function checkLifeCycleMethods(node) {
+      if (componentUtil.isES5Component(node, context) || componentUtil.isES6Component(node, context)) {
+        const methods = getLifeCycleMethods(node);
+        methods
+          .sort((a, b) => a.localeCompare(b))
+          .forEach((method) => checkUnsafe(node, method));
+      }
+    }
+
+    return {
+      ClassDeclaration: checkLifeCycleMethods,
+      ClassExpression: checkLifeCycleMethods,
+      ObjectExpression: checkLifeCycleMethods,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unstable-nested-components.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unstable-nested-components.d.ts","sourceRoot":"","sources":["no-unstable-nested-components.js"],"names":[],"mappings":"wBAsQW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unstable-nested-components.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,491 @@
+/**
+ * @fileoverview Prevent creating unstable components inside components
+ * @author Ari Perkkiö
+ */
+
+'use strict';
+
+const minimatch = require('minimatch');
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const COMPONENT_AS_PROPS_INFO = ' If you want to allow component creation in props, set allowAsProps option to true.';
+const HOOK_REGEXP = /^use[A-Z0-9].*$/;
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Generate error message with given parent component name
+ * @param {string} parentName Name of the parent component, if known
+ * @returns {string} Error message with parent component name
+ */
+function generateErrorMessageWithParentName(parentName) {
+  return `Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component${parentName ? ` “${parentName}” ` : ' '}and pass data as props.`;
+}
+
+/**
+ * Check whether given text matches the pattern passed in.
+ * @param {string} text Text to validate
+ * @param {string} pattern Pattern to match against
+ * @returns {boolean}
+ */
+function propMatchesRenderPropPattern(text, pattern) {
+  return typeof text === 'string' && minimatch(text, pattern);
+}
+
+/**
+ * Get closest parent matching given matcher
+ * @param {ASTNode} node The AST node
+ * @param {Context} context eslint context
+ * @param {Function} matcher Method used to match the parent
+ * @returns {ASTNode} The matching parent node, if any
+ */
+function getClosestMatchingParent(node, context, matcher) {
+  if (!node || !node.parent || node.parent.type === 'Program') {
+    return;
+  }
+
+  if (matcher(node.parent, context)) {
+    return node.parent;
+  }
+
+  return getClosestMatchingParent(node.parent, context, matcher);
+}
+
+/**
+ * Matcher used to check whether given node is a `createElement` call
+ * @param {ASTNode} node The AST node
+ * @param {Context} context eslint context
+ * @returns {boolean} True if node is a `createElement` call, false if not
+ */
+function isCreateElementMatcher(node, context) {
+  return (
+    astUtil.isCallExpression(node)
+    && isCreateElement(context, node)
+  );
+}
+
+/**
+ * Matcher used to check whether given node is a `ObjectExpression`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `ObjectExpression`, false if not
+ */
+function isObjectExpressionMatcher(node) {
+  return node && node.type === 'ObjectExpression';
+}
+
+/**
+ * Matcher used to check whether given node is a `JSXExpressionContainer`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `JSXExpressionContainer`, false if not
+ */
+function isJSXExpressionContainerMatcher(node) {
+  return node && node.type === 'JSXExpressionContainer';
+}
+
+/**
+ * Matcher used to check whether given node is a `JSXAttribute` of `JSXExpressionContainer`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `JSXAttribute` of `JSXExpressionContainer`, false if not
+ */
+function isJSXAttributeOfExpressionContainerMatcher(node) {
+  return (
+    node
+    && node.type === 'JSXAttribute'
+    && node.value
+    && node.value.type === 'JSXExpressionContainer'
+  );
+}
+
+/**
+ * Matcher used to check whether given node is an object `Property`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `Property`, false if not
+ */
+function isPropertyOfObjectExpressionMatcher(node) {
+  return (
+    node
+    && node.parent
+    && node.parent.type === 'Property'
+  );
+}
+
+/**
+ * Check whether given node or its parent is directly inside `map` call
+ * ```jsx
+ * {items.map(item => <li />)}
+ * ```
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is directly inside `map` call, false if not
+ */
+function isMapCall(node) {
+  return (
+    node
+    && node.callee
+    && node.callee.property
+    && node.callee.property.name === 'map'
+  );
+}
+
+/**
+ * Check whether given node is `ReturnStatement` of a React hook
+ * @param {ASTNode} node The AST node
+ * @param {Context} context eslint context
+ * @returns {boolean} True if node is a `ReturnStatement` of a React hook, false if not
+ */
+function isReturnStatementOfHook(node, context) {
+  if (
+    !node
+    || !node.parent
+    || node.parent.type !== 'ReturnStatement'
+  ) {
+    return false;
+  }
+
+  const callExpression = getClosestMatchingParent(node, context, astUtil.isCallExpression);
+  return (
+    callExpression
+    && callExpression.callee
+    && HOOK_REGEXP.test(callExpression.callee.name)
+  );
+}
+
+/**
+ * Check whether given node is declared inside a render prop
+ * ```jsx
+ * <Component renderFooter={() => <div />} />
+ * <Component>{() => <div />}</Component>
+ * ```
+ * @param {ASTNode} node The AST node
+ * @param {Context} context eslint context
+ * @param {string} propNamePattern a pattern to match render props against
+ * @returns {boolean} True if component is declared inside a render prop, false if not
+ */
+function isComponentInRenderProp(node, context, propNamePattern) {
+  if (
+    node
+    && node.parent
+    && node.parent.type === 'Property'
+    && node.parent.key
+    && propMatchesRenderPropPattern(node.parent.key.name, propNamePattern)
+  ) {
+    return true;
+  }
+
+  // Check whether component is a render prop used as direct children, e.g. <Component>{() => <div />}</Component>
+  if (
+    node
+    && node.parent
+    && node.parent.type === 'JSXExpressionContainer'
+    && node.parent.parent
+    && node.parent.parent.type === 'JSXElement'
+  ) {
+    return true;
+  }
+
+  const jsxExpressionContainer = getClosestMatchingParent(node, context, isJSXExpressionContainerMatcher);
+
+  // Check whether prop name indicates accepted patterns
+  if (
+    jsxExpressionContainer
+    && jsxExpressionContainer.parent
+    && jsxExpressionContainer.parent.type === 'JSXAttribute'
+    && jsxExpressionContainer.parent.name
+    && jsxExpressionContainer.parent.name.type === 'JSXIdentifier'
+  ) {
+    const propName = jsxExpressionContainer.parent.name.name;
+
+    // Starts with render, e.g. <Component renderFooter={() => <div />} />
+    if (propMatchesRenderPropPattern(propName, propNamePattern)) {
+      return true;
+    }
+
+    // Uses children prop explicitly, e.g. <Component children={() => <div />} />
+    if (propName === 'children') {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * Check whether given node is declared directly inside a render property
+ * ```jsx
+ * const rows = { render: () => <div /> }
+ * <Component rows={ [{ render: () => <div /> }] } />
+ *  ```
+ * @param {ASTNode} node The AST node
+ * @param {string} propNamePattern The pattern to match render props against
+ * @returns {boolean} True if component is declared inside a render property, false if not
+ */
+function isDirectValueOfRenderProperty(node, propNamePattern) {
+  return (
+    node
+    && node.parent
+    && node.parent.type === 'Property'
+    && node.parent.key
+    && node.parent.key.type === 'Identifier'
+    && propMatchesRenderPropPattern(node.parent.key.name, propNamePattern)
+  );
+}
+
+/**
+ * Resolve the component name of given node
+ * @param {ASTNode} node The AST node of the component
+ * @returns {string} Name of the component, if any
+ */
+function resolveComponentName(node) {
+  const parentName = node.id && node.id.name;
+  if (parentName) return parentName;
+
+  return (
+    node.type === 'ArrowFunctionExpression'
+    && node.parent
+    && node.parent.id
+    && node.parent.id.name
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow creating unstable components inside components',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-unstable-nested-components'),
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        customValidators: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        allowAsProps: {
+          type: 'boolean',
+        },
+        propNamePattern: {
+          type: 'string',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const allowAsProps = context.options.some((option) => option && option.allowAsProps);
+    const propNamePattern = (context.options[0] || {}).propNamePattern || 'render*';
+
+    /**
+     * Check whether given node is declared inside class component's render block
+     * ```jsx
+     * class Component extends React.Component {
+     *   render() {
+     *     class NestedClassComponent extends React.Component {
+     * ...
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {boolean} True if node is inside class component's render block, false if not
+     */
+    function isInsideRenderMethod(node) {
+      const parentComponent = utils.getParentComponent(node);
+
+      if (!parentComponent || parentComponent.type !== 'ClassDeclaration') {
+        return false;
+      }
+
+      return (
+        node
+        && node.parent
+        && node.parent.type === 'MethodDefinition'
+        && node.parent.key
+        && node.parent.key.name === 'render'
+      );
+    }
+
+    /**
+     * Check whether given node is a function component declared inside class component.
+     * Util's component detection fails to detect function components inside class components.
+     * ```jsx
+     * class Component extends React.Component {
+     *  render() {
+     *    const NestedComponent = () => <div />;
+     * ...
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {boolean} True if given node a function component declared inside class component, false if not
+     */
+    function isFunctionComponentInsideClassComponent(node) {
+      const parentComponent = utils.getParentComponent(node);
+      const parentStatelessComponent = utils.getParentStatelessComponent(node);
+
+      return (
+        parentComponent
+        && parentStatelessComponent
+        && parentComponent.type === 'ClassDeclaration'
+        && utils.getStatelessComponent(parentStatelessComponent)
+        && utils.isReturningJSX(node)
+      );
+    }
+
+    /**
+     * Check whether given node is declared inside `createElement` call's props
+     * ```js
+     * React.createElement(Component, {
+     *   footer: () => React.createElement("div", null)
+     * })
+     * ```
+     * @param {ASTNode} node The AST node
+     * @returns {boolean} True if node is declare inside `createElement` call's props, false if not
+     */
+    function isComponentInsideCreateElementsProp(node) {
+      if (!components.get(node)) {
+        return false;
+      }
+
+      const createElementParent = getClosestMatchingParent(node, context, isCreateElementMatcher);
+
+      return (
+        createElementParent
+        && createElementParent.arguments
+        && createElementParent.arguments[1] === getClosestMatchingParent(node, context, isObjectExpressionMatcher)
+      );
+    }
+
+    /**
+     * Check whether given node is declared inside a component/object prop.
+     * ```jsx
+     * <Component footer={() => <div />} />
+     * { footer: () => <div /> }
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {boolean} True if node is a component declared inside prop, false if not
+     */
+    function isComponentInProp(node) {
+      if (isPropertyOfObjectExpressionMatcher(node)) {
+        return utils.isReturningJSX(node);
+      }
+
+      const jsxAttribute = getClosestMatchingParent(node, context, isJSXAttributeOfExpressionContainerMatcher);
+
+      if (!jsxAttribute) {
+        return isComponentInsideCreateElementsProp(node);
+      }
+
+      return utils.isReturningJSX(node);
+    }
+
+    /**
+     * Check whether given node is a stateless component returning non-JSX
+     * ```jsx
+     * {{ a: () => null }}
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {boolean} True if node is a stateless component returning non-JSX, false if not
+     */
+    function isStatelessComponentReturningNull(node) {
+      const component = utils.getStatelessComponent(node);
+
+      return component && !utils.isReturningJSX(component);
+    }
+
+    /**
+     * Check whether given node is a unstable nested component
+     * @param {ASTNode} node The AST node being checked
+     */
+    function validate(node) {
+      if (!node || !node.parent) {
+        return;
+      }
+
+      const isDeclaredInsideProps = isComponentInProp(node);
+
+      if (
+        !components.get(node)
+        && !isFunctionComponentInsideClassComponent(node)
+        && !isDeclaredInsideProps) {
+        return;
+      }
+
+      if (
+        // Support allowAsProps option
+        (isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node, context, propNamePattern)))
+
+        // Prevent reporting components created inside Array.map calls
+        || isMapCall(node)
+        || isMapCall(node.parent)
+
+        // Do not mark components declared inside hooks (or falsy '() => null' clean-up methods)
+        || isReturnStatementOfHook(node, context)
+
+        // Do not mark objects containing render methods
+        || isDirectValueOfRenderProperty(node, propNamePattern)
+
+        // Prevent reporting nested class components twice
+        || isInsideRenderMethod(node)
+
+        // Prevent falsely reporting detected "components" which do not return JSX
+        || isStatelessComponentReturningNull(node)
+      ) {
+        return;
+      }
+
+      // Get the closest parent component
+      const parentComponent = getClosestMatchingParent(
+        node,
+        context,
+        (nodeToMatch) => components.get(nodeToMatch)
+      );
+
+      if (parentComponent) {
+        const parentName = resolveComponentName(parentComponent);
+
+        // Exclude lowercase parents, e.g. function createTestComponent()
+        // React-dom prevents creating lowercase components
+        if (parentName && parentName[0] === parentName[0].toLowerCase()) {
+          return;
+        }
+
+        let message = generateErrorMessageWithParentName(parentName);
+
+        // Add information about allowAsProps option when component is declared inside prop
+        if (isDeclaredInsideProps && !allowAsProps) {
+          message += COMPONENT_AS_PROPS_INFO;
+        }
+
+        report(context, message, null, {
+          node,
+        });
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      FunctionDeclaration(node) { validate(node); },
+      ArrowFunctionExpression(node) { validate(node); },
+      FunctionExpression(node) { validate(node); },
+      ClassDeclaration(node) { validate(node); },
+      CallExpression(node) { validate(node); },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unused-class-component-methods.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unused-class-component-methods.d.ts","sourceRoot":"","sources":["no-unused-class-component-methods.js"],"names":[],"mappings":"wBAoGW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-class-component-methods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,258 @@
+/**
+ * @fileoverview Prevent declaring unused methods and properties of component class
+ * @author Paweł Nowak, Berton Zhu
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const componentUtil = require('../util/componentUtil');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const LIFECYCLE_METHODS = new Set([
+  'constructor',
+  'componentDidCatch',
+  'componentDidMount',
+  'componentDidUpdate',
+  'componentWillMount',
+  'componentWillReceiveProps',
+  'componentWillUnmount',
+  'componentWillUpdate',
+  'getChildContext',
+  'getSnapshotBeforeUpdate',
+  'render',
+  'shouldComponentUpdate',
+  'UNSAFE_componentWillMount',
+  'UNSAFE_componentWillReceiveProps',
+  'UNSAFE_componentWillUpdate',
+]);
+
+const ES6_LIFECYCLE = new Set([
+  'state',
+]);
+
+const ES5_LIFECYCLE = new Set([
+  'getInitialState',
+  'getDefaultProps',
+  'mixins',
+]);
+
+function isKeyLiteralLike(node, property) {
+  return property.type === 'Literal'
+     || (property.type === 'TemplateLiteral' && property.expressions.length === 0)
+     || (node.computed === false && property.type === 'Identifier');
+}
+
+// Descend through all wrapping TypeCastExpressions and return the expression
+// that was cast.
+function uncast(node) {
+  while (node.type === 'TypeCastExpression') {
+    node = node.expression;
+  }
+  return node;
+}
+
+// Return the name of an identifier or the string value of a literal. Useful
+// anywhere that a literal may be used as a key (e.g., member expressions,
+// method definitions, ObjectExpression property keys).
+function getName(node) {
+  node = uncast(node);
+  const type = node.type;
+
+  if (type === 'Identifier') {
+    return node.name;
+  }
+  if (type === 'Literal') {
+    return String(node.value);
+  }
+  if (type === 'TemplateLiteral' && node.expressions.length === 0) {
+    return node.quasis[0].value.raw;
+  }
+  return null;
+}
+
+function isThisExpression(node) {
+  return uncast(node).type === 'ThisExpression';
+}
+
+function getInitialClassInfo(node, isClass) {
+  return {
+    classNode: node,
+    isClass,
+    // Set of nodes where properties were defined.
+    properties: new Set(),
+
+    // Set of names of properties that we've seen used.
+    usedProperties: new Set(),
+
+    inStatic: false,
+  };
+}
+
+const messages = {
+  unused: 'Unused method or property "{{name}}"',
+  unusedWithClass: 'Unused method or property "{{name}}" of class "{{className}}"',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow declaring unused methods of component class',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-unused-class-component-methods'),
+    },
+    messages,
+    schema: [],
+  },
+
+  create: ((context) => {
+    let classInfo = null;
+
+    // Takes an ObjectExpression node and adds all named Property nodes to the
+    // current set of properties.
+    function addProperty(node) {
+      classInfo.properties.add(node);
+    }
+
+    // Adds the name of the given node as a used property if the node is an
+    // Identifier or a Literal. Other node types are ignored.
+    function addUsedProperty(node) {
+      const name = getName(node);
+      if (name) {
+        classInfo.usedProperties.add(name);
+      }
+    }
+
+    function reportUnusedProperties() {
+      // Report all unused properties.
+      for (const node of classInfo.properties) { // eslint-disable-line no-restricted-syntax
+        const name = getName(node);
+        if (
+          !classInfo.usedProperties.has(name)
+           && !LIFECYCLE_METHODS.has(name)
+           && (classInfo.isClass ? !ES6_LIFECYCLE.has(name) : !ES5_LIFECYCLE.has(name))
+        ) {
+          const className = (classInfo.classNode.id && classInfo.classNode.id.name) || '';
+
+          const messageID = className ? 'unusedWithClass' : 'unused';
+          report(
+            context,
+            messages[messageID],
+            messageID,
+            {
+              node,
+              data: {
+                name,
+                className,
+              },
+            }
+          );
+        }
+      }
+    }
+
+    function exitMethod() {
+      if (!classInfo || !classInfo.inStatic) {
+        return;
+      }
+
+      classInfo.inStatic = false;
+    }
+
+    return {
+      ClassDeclaration(node) {
+        if (componentUtil.isES6Component(node, context)) {
+          classInfo = getInitialClassInfo(node, true);
+        }
+      },
+
+      ObjectExpression(node) {
+        if (componentUtil.isES5Component(node, context)) {
+          classInfo = getInitialClassInfo(node, false);
+        }
+      },
+
+      'ClassDeclaration:exit'() {
+        if (!classInfo) {
+          return;
+        }
+        reportUnusedProperties();
+        classInfo = null;
+      },
+
+      'ObjectExpression:exit'(node) {
+        if (!classInfo || classInfo.classNode !== node) {
+          return;
+        }
+        reportUnusedProperties();
+        classInfo = null;
+      },
+
+      Property(node) {
+        if (!classInfo || classInfo.classNode !== node.parent) {
+          return;
+        }
+
+        if (isKeyLiteralLike(node, node.key)) {
+          addProperty(node.key);
+        }
+      },
+
+      'ClassProperty, MethodDefinition, PropertyDefinition'(node) {
+        if (!classInfo) {
+          return;
+        }
+
+        if (node.static) {
+          classInfo.inStatic = true;
+          return;
+        }
+
+        if (isKeyLiteralLike(node, node.key)) {
+          addProperty(node.key);
+        }
+      },
+
+      'ClassProperty:exit': exitMethod,
+      'MethodDefinition:exit': exitMethod,
+      'PropertyDefinition:exit': exitMethod,
+
+      MemberExpression(node) {
+        if (!classInfo || classInfo.inStatic) {
+          return;
+        }
+
+        if (isThisExpression(node.object) && isKeyLiteralLike(node, node.property)) {
+          if (node.parent.type === 'AssignmentExpression' && node.parent.left === node) {
+            // detect `this.property = xxx`
+            addProperty(node.property);
+          } else {
+            // detect `this.property()`, `x = this.property`, etc.
+            addUsedProperty(node.property);
+          }
+        }
+      },
+
+      VariableDeclarator(node) {
+        if (!classInfo || classInfo.inStatic) {
+          return;
+        }
+
+        // detect `{ foo, bar: baz } = this`
+        if (node.init && isThisExpression(node.init) && node.id.type === 'ObjectPattern') {
+          node.id.properties
+            .filter((prop) => prop.type === 'Property' && isKeyLiteralLike(prop, prop.key))
+            .forEach((prop) => {
+              addUsedProperty('key' in prop ? prop.key : undefined);
+            });
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unused-prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unused-prop-types.d.ts","sourceRoot":"","sources":["no-unused-prop-types.js"],"names":[],"mappings":"wBAiCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,171 @@
+/**
+ * @fileoverview Prevent definitions of unused prop types
+ * @author Evgueni Naverniouk
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+// As for exceptions for props.children or props.className (and alike) look at
+// https://github.com/jsx-eslint/eslint-plugin-react/issues/7
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+/**
+ * Checks if the component must be validated
+ * @param {Object} component The component to process
+ * @returns {boolean} True if the component must be validated, false if not.
+ */
+function mustBeValidated(component) {
+  return !!component && !component.ignoreUnusedPropTypesValidation;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  unusedPropType: '\'{{name}}\' PropType is defined but prop is never used',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow definitions of unused propTypes',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-unused-prop-types'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignore: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+          uniqueItems: true,
+        },
+        customValidators: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        skipShapeProps: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    const defaults = { skipShapeProps: true, customValidators: [], ignore: [] };
+    const configuration = Object.assign({}, defaults, context.options[0] || {});
+
+    /**
+     * Checks if the prop is ignored
+     * @param {string} name Name of the prop to check.
+     * @returns {boolean} True if the prop is ignored, false if not.
+     */
+    function isIgnored(name) {
+      return configuration.ignore.indexOf(name) !== -1;
+    }
+
+    /**
+     * Checks if a prop is used
+     * @param {ASTNode} node The AST node being checked.
+     * @param {Object} prop Declared prop object
+     * @returns {boolean} True if the prop is used, false if not.
+     */
+    function isPropUsed(node, prop) {
+      const usedPropTypes = node.usedPropTypes || [];
+      for (let i = 0, l = usedPropTypes.length; i < l; i++) {
+        const usedProp = usedPropTypes[i];
+        if (
+          prop.type === 'shape'
+          || prop.type === 'exact'
+          || prop.name === '__ANY_KEY__'
+          || usedProp.name === prop.name
+        ) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Used to recursively loop through each declared prop type
+     * @param {Object} component The component to process
+     * @param {ASTNode[]|true} props List of props to validate
+     */
+    function reportUnusedPropType(component, props) {
+      // Skip props that check instances
+      if (props === true) {
+        return;
+      }
+
+      Object.keys(props || {}).forEach((key) => {
+        const prop = props[key];
+        // Skip props that check instances
+        if (prop === true) {
+          return;
+        }
+
+        if ((prop.type === 'shape' || prop.type === 'exact') && configuration.skipShapeProps) {
+          return;
+        }
+
+        if (prop.node && prop.node.typeAnnotation && prop.node.typeAnnotation.typeAnnotation
+          && prop.node.typeAnnotation.typeAnnotation.type === 'TSNeverKeyword') {
+          return;
+        }
+
+        if (prop.node && !isIgnored(prop.fullName) && !isPropUsed(component, prop)) {
+          report(context, messages.unusedPropType, 'unusedPropType', {
+            node: prop.node.key || prop.node,
+            data: {
+              name: prop.fullName,
+            },
+          });
+        }
+
+        if (prop.children) {
+          reportUnusedPropType(component, prop.children);
+        }
+      });
+    }
+
+    /**
+     * Reports unused proptypes for a given component
+     * @param {Object} component The component to process
+     */
+    function reportUnusedPropTypes(component) {
+      reportUnusedPropType(component, component.declaredPropTypes);
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      'Program:exit'() {
+        // Report undeclared proptypes for all classes
+        values(components.list())
+          .filter((component) => mustBeValidated(component))
+          .forEach((component) => {
+            reportUnusedPropTypes(component);
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-unused-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-unused-state.d.ts","sourceRoot":"","sources":["no-unused-state.js"],"names":[],"mappings":"wBAgFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-unused-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,529 @@
+/**
+ * @fileoverview  Attempts to discover all state fields in a React component and
+ * warn if any of them are never read.
+ *
+ * State field definitions are collected from `this.state = {}` assignments in
+ * the constructor, objects passed to `this.setState()`, and `state = {}` class
+ * property assignments.
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const report = require('../util/report');
+const getScope = require('../util/eslint').getScope;
+
+// Descend through all wrapping TypeCastExpressions and return the expression
+// that was cast.
+function uncast(node) {
+  while (node.type === 'TypeCastExpression') {
+    node = node.expression;
+  }
+  return node;
+}
+
+// Return the name of an identifier or the string value of a literal. Useful
+// anywhere that a literal may be used as a key (e.g., member expressions,
+// method definitions, ObjectExpression property keys).
+function getName(node) {
+  node = uncast(node);
+  const type = node.type;
+
+  if (type === 'Identifier') {
+    return node.name;
+  }
+  if (type === 'Literal') {
+    return String(node.value);
+  }
+  if (type === 'TemplateLiteral' && node.expressions.length === 0) {
+    return node.quasis[0].value.raw;
+  }
+  return null;
+}
+
+function isThisExpression(node) {
+  return astUtil.unwrapTSAsExpression(uncast(node)).type === 'ThisExpression';
+}
+
+function getInitialClassInfo() {
+  return {
+    // Set of nodes where state fields were defined.
+    stateFields: new Set(),
+
+    // Set of names of state fields that we've seen used.
+    usedStateFields: new Set(),
+
+    // Names of local variables that may be pointing to this.state. To
+    // track this properly, we would need to keep track of all locals,
+    // shadowing, assignments, etc. To keep things simple, we only
+    // maintain one set of aliases per method and accept that it will
+    // produce some false negatives.
+    aliases: null,
+  };
+}
+
+function isSetStateCall(node) {
+  const unwrappedCalleeNode = astUtil.unwrapTSAsExpression(node.callee);
+
+  return (
+    unwrappedCalleeNode.type === 'MemberExpression'
+    && isThisExpression(unwrappedCalleeNode.object)
+    && getName(unwrappedCalleeNode.property) === 'setState'
+  );
+}
+
+const messages = {
+  unusedStateField: 'Unused state field: \'{{name}}\'',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow definitions of unused state',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('no-unused-state'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    // Non-null when we are inside a React component ClassDeclaration and we have
+    // not yet encountered any use of this.state which we have chosen not to
+    // analyze. If we encounter any such usage (like this.state being spread as
+    // JSX attributes), then this is again set to null.
+    let classInfo = null;
+
+    function isStateParameterReference(node) {
+      const classMethods = [
+        'shouldComponentUpdate',
+        'componentWillUpdate',
+        'UNSAFE_componentWillUpdate',
+        'getSnapshotBeforeUpdate',
+        'componentDidUpdate',
+      ];
+
+      let scope = getScope(context, node);
+      while (scope) {
+        const parent = scope.block && scope.block.parent;
+        if (
+          parent
+          && parent.type === 'MethodDefinition' && (
+            (parent.static && parent.key.name === 'getDerivedStateFromProps')
+            || classMethods.indexOf(parent.key.name) !== -1
+          )
+          && parent.value.type === 'FunctionExpression'
+          && parent.value.params[1]
+          && parent.value.params[1].name === node.name
+        ) {
+          return true;
+        }
+        scope = scope.upper;
+      }
+
+      return false;
+    }
+
+    // Returns true if the given node is possibly a reference to `this.state` or the state parameter of
+    // a lifecycle method.
+    function isStateReference(node) {
+      node = uncast(node);
+
+      const isDirectStateReference = node.type === 'MemberExpression'
+        && isThisExpression(node.object)
+        && node.property.name === 'state';
+
+      const isAliasedStateReference = node.type === 'Identifier'
+        && classInfo.aliases
+        && classInfo.aliases.has(node.name);
+
+      return isDirectStateReference || isAliasedStateReference || isStateParameterReference(node);
+    }
+
+    // Takes an ObjectExpression node and adds all named Property nodes to the
+    // current set of state fields.
+    function addStateFields(node) {
+      node.properties.filter((prop) => (
+        prop.type === 'Property'
+          && (prop.key.type === 'Literal'
+          || (prop.key.type === 'TemplateLiteral' && prop.key.expressions.length === 0)
+          || (prop.computed === false && prop.key.type === 'Identifier'))
+          && getName(prop.key) !== null
+      )).forEach((prop) => {
+        classInfo.stateFields.add(prop);
+      });
+    }
+
+    // Adds the name of the given node as a used state field if the node is an
+    // Identifier or a Literal. Other node types are ignored.
+    function addUsedStateField(node) {
+      if (!classInfo) {
+        return;
+      }
+      const name = getName(node);
+      if (name) {
+        classInfo.usedStateFields.add(name);
+      }
+    }
+
+    // Records used state fields and new aliases for an ObjectPattern which
+    // destructures `this.state`.
+    function handleStateDestructuring(node) {
+      node.properties.forEach((prop) => {
+        if (prop.type === 'Property') {
+          addUsedStateField(prop.key);
+        } else if (
+          (prop.type === 'ExperimentalRestProperty' || prop.type === 'RestElement')
+          && classInfo.aliases
+        ) {
+          classInfo.aliases.add(getName(prop.argument));
+        }
+      });
+    }
+
+    // Used to record used state fields and new aliases for both
+    // AssignmentExpressions and VariableDeclarators.
+    function handleAssignment(left, right) {
+      const unwrappedRight = astUtil.unwrapTSAsExpression(right);
+
+      switch (left.type) {
+        case 'Identifier':
+          if (isStateReference(unwrappedRight) && classInfo.aliases) {
+            classInfo.aliases.add(left.name);
+          }
+          break;
+        case 'ObjectPattern':
+          if (isStateReference(unwrappedRight)) {
+            handleStateDestructuring(left);
+          } else if (isThisExpression(unwrappedRight) && classInfo.aliases) {
+            left.properties.forEach((prop) => {
+              if (prop.type === 'Property' && getName(prop.key) === 'state') {
+                const name = getName(prop.value);
+                if (name) {
+                  classInfo.aliases.add(name);
+                } else if (prop.value.type === 'ObjectPattern') {
+                  handleStateDestructuring(prop.value);
+                }
+              }
+            });
+          }
+          break;
+        default:
+        // pass
+      }
+    }
+
+    function reportUnusedFields() {
+      // Report all unused state fields.
+      classInfo.stateFields.forEach((node) => {
+        const name = getName(node.key);
+        if (!classInfo.usedStateFields.has(name)) {
+          report(context, messages.unusedStateField, 'unusedStateField', {
+            node,
+            data: {
+              name,
+            },
+          });
+        }
+      });
+    }
+
+    function handleES6ComponentEnter(node) {
+      if (componentUtil.isES6Component(node, context)) {
+        classInfo = getInitialClassInfo();
+      }
+    }
+
+    function handleES6ComponentExit() {
+      if (!classInfo) {
+        return;
+      }
+      reportUnusedFields();
+      classInfo = null;
+    }
+
+    function isGDSFP(node) {
+      const name = getName(node.key);
+      if (
+        !node.static
+        || name !== 'getDerivedStateFromProps'
+        || !node.value
+        || !node.value.params
+        || node.value.params.length < 2 // no `state` argument
+      ) {
+        return false;
+      }
+      return true;
+    }
+
+    return {
+      ClassDeclaration: handleES6ComponentEnter,
+
+      'ClassDeclaration:exit': handleES6ComponentExit,
+
+      ClassExpression: handleES6ComponentEnter,
+
+      'ClassExpression:exit': handleES6ComponentExit,
+
+      ObjectExpression(node) {
+        if (componentUtil.isES5Component(node, context)) {
+          classInfo = getInitialClassInfo();
+        }
+      },
+
+      'ObjectExpression:exit'(node) {
+        if (!classInfo) {
+          return;
+        }
+
+        if (componentUtil.isES5Component(node, context)) {
+          reportUnusedFields();
+          classInfo = null;
+        }
+      },
+
+      CallExpression(node) {
+        if (!classInfo) {
+          return;
+        }
+
+        const unwrappedNode = astUtil.unwrapTSAsExpression(node);
+        const unwrappedArgumentNode = astUtil.unwrapTSAsExpression(unwrappedNode.arguments[0]);
+
+        // If we're looking at a `this.setState({})` invocation, record all the
+        // properties as state fields.
+        if (
+          isSetStateCall(unwrappedNode)
+          && unwrappedNode.arguments.length > 0
+          && unwrappedArgumentNode.type === 'ObjectExpression'
+        ) {
+          addStateFields(unwrappedArgumentNode);
+        } else if (
+          isSetStateCall(unwrappedNode)
+          && unwrappedNode.arguments.length > 0
+          && unwrappedArgumentNode.type === 'ArrowFunctionExpression'
+        ) {
+          const unwrappedBodyNode = astUtil.unwrapTSAsExpression(unwrappedArgumentNode.body);
+
+          if (unwrappedBodyNode.type === 'ObjectExpression') {
+            addStateFields(unwrappedBodyNode);
+          }
+          if (unwrappedArgumentNode.params.length > 0 && classInfo.aliases) {
+            const firstParam = unwrappedArgumentNode.params[0];
+            if (firstParam.type === 'ObjectPattern') {
+              handleStateDestructuring(firstParam);
+            } else {
+              classInfo.aliases.add(getName(firstParam));
+            }
+          }
+        }
+      },
+
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!classInfo) {
+          return;
+        }
+        // If we see state being assigned as a class property using an object
+        // expression, record all the fields of that object as state fields.
+        const unwrappedValueNode = astUtil.unwrapTSAsExpression(node.value);
+
+        const name = getName(node.key);
+        if (
+          name === 'state'
+          && !node.static
+          && unwrappedValueNode
+          && unwrappedValueNode.type === 'ObjectExpression'
+        ) {
+          addStateFields(unwrappedValueNode);
+        }
+
+        if (
+          !node.static
+          && unwrappedValueNode
+          && unwrappedValueNode.type === 'ArrowFunctionExpression'
+        ) {
+          // Create a new set for this.state aliases local to this method.
+          classInfo.aliases = new Set();
+        }
+      },
+
+      'ClassProperty:exit'(node) {
+        if (
+          classInfo
+          && !node.static
+          && node.value
+          && node.value.type === 'ArrowFunctionExpression'
+        ) {
+          // Forget our set of local aliases.
+          classInfo.aliases = null;
+        }
+      },
+
+      'PropertyDefinition, ClassProperty'(node) {
+        if (!isGDSFP(node)) {
+          return;
+        }
+
+        const childScope = getScope(context, node).childScopes.find((x) => x.block === node.value);
+        if (!childScope) {
+          return;
+        }
+        const scope = childScope.variableScope.childScopes.find((x) => x.block === node.value);
+        const stateArg = node.value.params[1]; // probably "state"
+        if (!scope || !scope.variables) {
+          return;
+        }
+        const argVar = scope.variables.find((x) => x.name === stateArg.name);
+
+        if (argVar) {
+          const stateRefs = argVar.references;
+
+          stateRefs.forEach((ref) => {
+            const identifier = ref.identifier;
+            if (identifier && identifier.parent && identifier.parent.type === 'MemberExpression') {
+              addUsedStateField(identifier.parent.property);
+            }
+          });
+        }
+      },
+
+      'PropertyDefinition:exit'(node) {
+        if (
+          classInfo
+          && !node.static
+          && node.value
+          && node.value.type === 'ArrowFunctionExpression'
+          && !isGDSFP(node)
+        ) {
+          // Forget our set of local aliases.
+          classInfo.aliases = null;
+        }
+      },
+
+      MethodDefinition() {
+        if (!classInfo) {
+          return;
+        }
+        // Create a new set for this.state aliases local to this method.
+        classInfo.aliases = new Set();
+      },
+
+      'MethodDefinition:exit'() {
+        if (!classInfo) {
+          return;
+        }
+        // Forget our set of local aliases.
+        classInfo.aliases = null;
+      },
+
+      FunctionExpression(node) {
+        if (!classInfo) {
+          return;
+        }
+
+        const parent = node.parent;
+        if (!componentUtil.isES5Component(parent.parent, context)) {
+          return;
+        }
+
+        if (
+          'key' in parent
+          && 'name' in parent.key
+          && parent.key.name === 'getInitialState'
+        ) {
+          const body = node.body.body;
+          const lastBodyNode = body[body.length - 1];
+
+          if (
+            lastBodyNode.type === 'ReturnStatement'
+            && lastBodyNode.argument.type === 'ObjectExpression'
+          ) {
+            addStateFields(lastBodyNode.argument);
+          }
+        } else {
+          // Create a new set for this.state aliases local to this method.
+          classInfo.aliases = new Set();
+        }
+      },
+
+      AssignmentExpression(node) {
+        if (!classInfo) {
+          return;
+        }
+
+        const unwrappedLeft = astUtil.unwrapTSAsExpression(node.left);
+        const unwrappedRight = astUtil.unwrapTSAsExpression(node.right);
+
+        // Check for assignments like `this.state = {}`
+        if (
+          unwrappedLeft.type === 'MemberExpression'
+          && isThisExpression(unwrappedLeft.object)
+          && getName(unwrappedLeft.property) === 'state'
+          && unwrappedRight.type === 'ObjectExpression'
+        ) {
+          // Find the nearest function expression containing this assignment.
+          /** @type {import('eslint').Rule.Node} */
+          let fn = node;
+          while (fn.type !== 'FunctionExpression' && fn.parent) {
+            fn = fn.parent;
+          }
+          // If the nearest containing function is the constructor, then we want
+          // to record all the assigned properties as state fields.
+          if (
+            fn.parent
+            && fn.parent.type === 'MethodDefinition'
+            && fn.parent.kind === 'constructor'
+          ) {
+            addStateFields(unwrappedRight);
+          }
+        } else {
+          // Check for assignments like `alias = this.state` and record the alias.
+          handleAssignment(unwrappedLeft, unwrappedRight);
+        }
+      },
+
+      VariableDeclarator(node) {
+        if (!classInfo || !node.init) {
+          return;
+        }
+        handleAssignment(node.id, node.init);
+      },
+
+      'MemberExpression, OptionalMemberExpression'(node) {
+        if (!classInfo) {
+          return;
+        }
+        if (isStateReference(astUtil.unwrapTSAsExpression(node.object))) {
+          // If we see this.state[foo] access, give up.
+          if (node.computed && node.property.type !== 'Literal') {
+            classInfo = null;
+            return;
+          }
+          // Otherwise, record that we saw this property being accessed.
+          addUsedStateField(node.property);
+        // If we see a `this.state` access in a CallExpression, give up.
+        } else if (isStateReference(node) && astUtil.isCallExpression(node.parent)) {
+          classInfo = null;
+        }
+      },
+
+      JSXSpreadAttribute(node) {
+        if (classInfo && isStateReference(node.argument)) {
+          classInfo = null;
+        }
+      },
+
+      'ExperimentalSpreadProperty, SpreadElement'(node) {
+        if (classInfo && isStateReference(node.argument)) {
+          classInfo = null;
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=no-will-update-set-state.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"no-will-update-set-state.d.ts","sourceRoot":"","sources":["no-will-update-set-state.js"],"names":[],"mappings":"wBAUW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/no-will-update-set-state.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+/**
+ * @fileoverview Prevent usage of setState in componentWillUpdate
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule');
+const testReactVersion = require('../util/version').testReactVersion;
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = makeNoMethodSetStateRule(
+  'componentWillUpdate',
+  (context) => testReactVersion(context, '>= 16.3.0')
+);
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=prefer-es6-class.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"prefer-es6-class.d.ts","sourceRoot":"","sources":["prefer-es6-class.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-es6-class.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Enforce ES5 or ES6 class for React Components
+ * @author Dan Hamilton
+ */
+
+'use strict';
+
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  shouldUseES6Class: 'Component should use es6 class instead of createClass',
+  shouldUseCreateClass: 'Component should use createClass instead of es6 class',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce ES5 or ES6 class for React Components',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('prefer-es6-class'),
+    },
+
+    messages,
+
+    schema: [{
+      enum: ['always', 'never'],
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || 'always';
+
+    return {
+      ObjectExpression(node) {
+        if (componentUtil.isES5Component(node, context) && configuration === 'always') {
+          report(context, messages.shouldUseES6Class, 'shouldUseES6Class', {
+            node,
+          });
+        }
+      },
+      ClassDeclaration(node) {
+        if (componentUtil.isES6Component(node, context) && configuration === 'never') {
+          report(context, messages.shouldUseCreateClass, 'shouldUseCreateClass', {
+            node,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=prefer-exact-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"prefer-exact-props.d.ts","sourceRoot":"","sources":["prefer-exact-props.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-exact-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,162 @@
+/**
+ * @fileoverview Prefer exact proptype definitions
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const propsUtil = require('../util/props');
+const propWrapperUtil = require('../util/propWrapper');
+const variableUtil = require('../util/variable');
+const report = require('../util/report');
+const getText = require('../util/eslint').getText;
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+const messages = {
+  propTypes: 'Component propTypes should be exact by using {{exactPropWrappers}}.',
+  flow: 'Component flow props should be set with exact objects.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Prefer exact proptype definitions',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('prefer-exact-props'),
+    },
+    messages,
+    schema: [],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const typeAliases = {};
+    const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context);
+
+    function getPropTypesErrorMessage() {
+      const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers);
+      const message = exactWrappers.size > 1 ? `one of ${formattedWrappers}` : formattedWrappers;
+      return { exactPropWrappers: message };
+    }
+
+    function isNonExactObjectTypeAnnotation(node) {
+      return (
+        node
+        && node.type === 'ObjectTypeAnnotation'
+        && node.properties.length > 0
+        && !node.exact
+      );
+    }
+
+    function hasNonExactObjectTypeAnnotation(node) {
+      const typeAnnotation = node.typeAnnotation;
+      return (
+        typeAnnotation
+        && typeAnnotation.typeAnnotation
+        && isNonExactObjectTypeAnnotation(typeAnnotation.typeAnnotation)
+      );
+    }
+
+    function hasGenericTypeAnnotation(node) {
+      const typeAnnotation = node.typeAnnotation;
+      return (
+        typeAnnotation
+        && typeAnnotation.typeAnnotation
+        && typeAnnotation.typeAnnotation.type === 'GenericTypeAnnotation'
+      );
+    }
+
+    function isNonEmptyObjectExpression(node) {
+      return (
+        node
+        && node.type === 'ObjectExpression'
+        && node.properties.length > 0
+      );
+    }
+
+    function isNonExactPropWrapperFunction(node) {
+      return (
+        astUtil.isCallExpression(node)
+        && !propWrapperUtil.isExactPropWrapperFunction(context, getText(context, node.callee))
+      );
+    }
+
+    function reportPropTypesError(node) {
+      report(context, messages.propTypes, 'propTypes', {
+        node,
+        data: getPropTypesErrorMessage(),
+      });
+    }
+
+    function reportFlowError(node) {
+      report(context, messages.flow, 'flow', {
+        node,
+      });
+    }
+
+    return {
+      TypeAlias(node) {
+        // working around an issue with eslint@3 and babel-eslint not finding the TypeAlias in scope
+        typeAliases[node.id.name] = node;
+      },
+
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!propsUtil.isPropTypesDeclaration(node)) {
+          return;
+        }
+
+        if (hasNonExactObjectTypeAnnotation(node)) {
+          reportFlowError(node);
+        } else if (exactWrappers.size > 0 && isNonEmptyObjectExpression(node.value)) {
+          reportPropTypesError(node);
+        } else if (exactWrappers.size > 0 && isNonExactPropWrapperFunction(node.value)) {
+          reportPropTypesError(node);
+        }
+      },
+
+      Identifier(node) {
+        if (!utils.getStatelessComponent(node.parent)) {
+          return;
+        }
+
+        if (hasNonExactObjectTypeAnnotation(node)) {
+          reportFlowError(node);
+        } else if (hasGenericTypeAnnotation(node)) {
+          const identifier = node.typeAnnotation.typeAnnotation.id.name;
+          const typeAlias = typeAliases[identifier];
+          const propsDefinition = typeAlias ? typeAlias.right : null;
+          if (isNonExactObjectTypeAnnotation(propsDefinition)) {
+            reportFlowError(node);
+          }
+        }
+      },
+
+      MemberExpression(node) {
+        if (!propsUtil.isPropTypesDeclaration(node) || exactWrappers.size === 0) {
+          return;
+        }
+
+        const right = node.parent.right;
+        if (isNonEmptyObjectExpression(right)) {
+          reportPropTypesError(node);
+        } else if (isNonExactPropWrapperFunction(right)) {
+          reportPropTypesError(node);
+        } else if (right.type === 'Identifier') {
+          const identifier = right.name;
+          const propsDefinition = variableUtil.findVariableByName(context, node, identifier);
+          if (isNonEmptyObjectExpression(propsDefinition)) {
+            reportPropTypesError(node);
+          } else if (isNonExactPropWrapperFunction(propsDefinition)) {
+            reportPropTypesError(node);
+          }
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=prefer-read-only-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"prefer-read-only-props.d.ts","sourceRoot":"","sources":["prefer-read-only-props.js"],"names":[],"mappings":"wBAiDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-read-only-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Require component props to be typed as read-only.
+ * @author Luke Zapart
+ */
+
+'use strict';
+
+const flatMap = require('array.prototype.flatmap');
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+function isFlowPropertyType(node) {
+  return node.type === 'ObjectTypeProperty';
+}
+
+function isTypescriptPropertyType(node) {
+  return node.type === 'TSPropertySignature';
+}
+
+function isCovariant(node) {
+  return (node.variance && node.variance.kind === 'plus')
+    || (
+      node.parent
+      && node.parent.parent
+      && node.parent.parent.parent
+      && node.parent.parent.parent.id
+      && node.parent.parent.parent.id.name === '$ReadOnly'
+    );
+}
+
+function isReadonly(node) {
+  return (
+    node.typeAnnotation
+    && node.typeAnnotation.parent
+    && node.typeAnnotation.parent.readonly
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  readOnlyProp: 'Prop \'{{name}}\' should be read-only.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce that props are read-only',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('prefer-read-only-props'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [],
+  },
+
+  create: Components.detect((context, components) => {
+    function reportReadOnlyProp(prop, propName, fixer) {
+      report(context, messages.readOnlyProp, 'readOnlyProp', {
+        node: prop.node,
+        data: {
+          name: propName,
+        },
+        fix: fixer,
+      });
+    }
+
+    return {
+      'Program:exit'() {
+        flatMap(
+          values(components.list()),
+          (component) => component.declaredPropTypes || []
+        ).forEach((declaredPropTypes) => {
+          Object.keys(declaredPropTypes).forEach((propName) => {
+            const prop = declaredPropTypes[propName];
+            if (!prop.node) {
+              return;
+            }
+
+            if (isFlowPropertyType(prop.node)) {
+              if (!isCovariant(prop.node)) {
+                reportReadOnlyProp(prop, propName, (fixer) => {
+                  if (!prop.node.variance) {
+                    // Insert covariance
+                    return fixer.insertTextBefore(prop.node, '+');
+                  }
+
+                  // Replace contravariance with covariance
+                  return fixer.replaceText(prop.node.variance, '+');
+                });
+              }
+
+              return;
+            }
+
+            if (isTypescriptPropertyType(prop.node)) {
+              if (!isReadonly(prop.node)) {
+                reportReadOnlyProp(prop, propName, (fixer) => (
+                  fixer.insertTextBefore(prop.node, 'readonly ')
+                ));
+              }
+            }
+          });
+        });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=prefer-stateless-function.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"prefer-stateless-function.d.ts","sourceRoot":"","sources":["prefer-stateless-function.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prefer-stateless-function.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,397 @@
+/**
+ * @fileoverview Enforce stateless components to be written as a pure function
+ * @author Yannick Croissant
+ * @author Alberto Rodríguez
+ * @copyright 2015 Alberto Rodríguez. All rights reserved.
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const testReactVersion = require('../util/version').testReactVersion;
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getScope = eslintUtil.getScope;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  componentShouldBePure: 'Component should be written as a pure function',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce stateless components to be written as a pure function',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('prefer-stateless-function'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignorePureComponents: {
+          default: false,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const configuration = context.options[0] || {};
+    const ignorePureComponents = configuration.ignorePureComponents || false;
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    /**
+     * Checks whether a given array of statements is a single call of `super`.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode[]} body - An array of statements to check.
+     * @returns {boolean} `true` if the body is a single call of `super`.
+     */
+    function isSingleSuperCall(body) {
+      return (
+        body.length === 1
+        && body[0].type === 'ExpressionStatement'
+        && astUtil.isCallExpression(body[0].expression)
+        && body[0].expression.callee.type === 'Super'
+      );
+    }
+
+    /**
+     * Checks whether a given node is a pattern which doesn't have any side effects.
+     * Default parameters and Destructuring parameters can have side effects.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode} node - A pattern node.
+     * @returns {boolean} `true` if the node doesn't have any side effects.
+     */
+    function isSimple(node) {
+      return node.type === 'Identifier' || node.type === 'RestElement';
+    }
+
+    /**
+     * Checks whether a given array of expressions is `...arguments` or not.
+     * `super(...arguments)` passes all arguments through.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode[]} superArgs - An array of expressions to check.
+     * @returns {boolean} `true` if the superArgs is `...arguments`.
+     */
+    function isSpreadArguments(superArgs) {
+      return (
+        superArgs.length === 1
+        && superArgs[0].type === 'SpreadElement'
+        && superArgs[0].argument.type === 'Identifier'
+        && superArgs[0].argument.name === 'arguments'
+      );
+    }
+
+    /**
+     * Checks whether given 2 nodes are identifiers which have the same name or not.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode} ctorParam - A node to check.
+     * @param {ASTNode} superArg - A node to check.
+     * @returns {boolean} `true` if the nodes are identifiers which have the same
+     *      name.
+     */
+    function isValidIdentifierPair(ctorParam, superArg) {
+      return (
+        ctorParam.type === 'Identifier'
+        && superArg.type === 'Identifier'
+        && ctorParam.name === superArg.name
+      );
+    }
+
+    /**
+     * Checks whether given 2 nodes are a rest/spread pair which has the same values.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode} ctorParam - A node to check.
+     * @param {ASTNode} superArg - A node to check.
+     * @returns {boolean} `true` if the nodes are a rest/spread pair which has the
+     *      same values.
+     */
+    function isValidRestSpreadPair(ctorParam, superArg) {
+      return (
+        ctorParam.type === 'RestElement'
+        && superArg.type === 'SpreadElement'
+        && isValidIdentifierPair(ctorParam.argument, superArg.argument)
+      );
+    }
+
+    /**
+     * Checks whether given 2 nodes have the same value or not.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode} ctorParam - A node to check.
+     * @param {ASTNode} superArg - A node to check.
+     * @returns {boolean} `true` if the nodes have the same value or not.
+     */
+    function isValidPair(ctorParam, superArg) {
+      return (
+        isValidIdentifierPair(ctorParam, superArg)
+        || isValidRestSpreadPair(ctorParam, superArg)
+      );
+    }
+
+    /**
+     * Checks whether the parameters of a constructor and the arguments of `super()`
+     * have the same values or not.
+     * @see eslint no-useless-constructor rule
+     * @param {ASTNode[]} ctorParams - The parameters of a constructor to check.
+     * @param {ASTNode} superArgs - The arguments of `super()` to check.
+     * @returns {boolean} `true` if those have the same values.
+     */
+    function isPassingThrough(ctorParams, superArgs) {
+      if (ctorParams.length !== superArgs.length) {
+        return false;
+      }
+
+      for (let i = 0; i < ctorParams.length; ++i) {
+        if (!isValidPair(ctorParams[i], superArgs[i])) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    /**
+     * Checks whether the constructor body is a redundant super call.
+     * @see eslint no-useless-constructor rule
+     * @param {Array} body - constructor body content.
+     * @param {Array} ctorParams - The params to check against super call.
+     * @returns {boolean} true if the constructor body is redundant
+     */
+    function isRedundantSuperCall(body, ctorParams) {
+      return (
+        isSingleSuperCall(body)
+        && ctorParams.every(isSimple)
+        && (
+          isSpreadArguments(body[0].expression.arguments)
+          || isPassingThrough(ctorParams, body[0].expression.arguments)
+        )
+      );
+    }
+
+    /**
+     * Check if a given AST node have any other properties the ones available in stateless components
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if the node has at least one other property, false if not.
+     */
+    function hasOtherProperties(node) {
+      const properties = astUtil.getComponentProperties(node);
+      return properties.some((property) => {
+        const name = astUtil.getPropertyName(property);
+        const isDisplayName = name === 'displayName';
+        const isPropTypes = name === 'propTypes' || ((name === 'props') && property.typeAnnotation);
+        const contextTypes = name === 'contextTypes';
+        const defaultProps = name === 'defaultProps';
+        const isUselessConstructor = property.kind === 'constructor'
+          && !!property.value.body
+          && isRedundantSuperCall(property.value.body.body, property.value.params);
+        const isRender = name === 'render';
+        return !isDisplayName && !isPropTypes && !contextTypes && !defaultProps && !isUselessConstructor && !isRender;
+      });
+    }
+
+    /**
+     * Mark component as pure as declared
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markSCUAsDeclared(node) {
+      components.set(node, {
+        hasSCU: true,
+      });
+    }
+
+    /**
+     * Mark childContextTypes as declared
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markChildContextTypesAsDeclared(node) {
+      components.set(node, {
+        hasChildContextTypes: true,
+      });
+    }
+
+    /**
+     * Mark a setState as used
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markThisAsUsed(node) {
+      components.set(node, {
+        useThis: true,
+      });
+    }
+
+    /**
+     * Mark a props or context as used
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markPropsOrContextAsUsed(node) {
+      components.set(node, {
+        usePropsOrContext: true,
+      });
+    }
+
+    /**
+     * Mark a ref as used
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markRefAsUsed(node) {
+      components.set(node, {
+        useRef: true,
+      });
+    }
+
+    /**
+     * Mark return as invalid
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markReturnAsInvalid(node) {
+      components.set(node, {
+        invalidReturn: true,
+      });
+    }
+
+    /**
+     * Mark a ClassDeclaration as having used decorators
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markDecoratorsAsUsed(node) {
+      components.set(node, {
+        useDecorators: true,
+      });
+    }
+
+    function visitClass(node) {
+      if (ignorePureComponents && componentUtil.isPureComponent(node, context)) {
+        markSCUAsDeclared(node);
+      }
+
+      if (node.decorators && node.decorators.length) {
+        markDecoratorsAsUsed(node);
+      }
+    }
+
+    return {
+      ClassDeclaration: visitClass,
+      ClassExpression: visitClass,
+
+      // Mark `this` destructuring as a usage of `this`
+      VariableDeclarator(node) {
+        // Ignore destructuring on other than `this`
+        if (!node.id || node.id.type !== 'ObjectPattern' || !node.init || node.init.type !== 'ThisExpression') {
+          return;
+        }
+        // Ignore `props` and `context`
+        const useThis = node.id.properties.some((property) => {
+          const name = astUtil.getPropertyName(property);
+          return name !== 'props' && name !== 'context';
+        });
+        if (!useThis) {
+          markPropsOrContextAsUsed(node);
+          return;
+        }
+        markThisAsUsed(node);
+      },
+
+      // Mark `this` usage
+      MemberExpression(node) {
+        if (node.object.type !== 'ThisExpression') {
+          if (node.property && node.property.name === 'childContextTypes') {
+            const component = utils.getRelatedComponent(node);
+            if (!component) {
+              return;
+            }
+            markChildContextTypesAsDeclared(component.node);
+          }
+          return;
+        // Ignore calls to `this.props` and `this.context`
+        }
+        if (
+          (node.property.name || node.property.value) === 'props'
+          || (node.property.name || node.property.value) === 'context'
+        ) {
+          markPropsOrContextAsUsed(node);
+          return;
+        }
+        markThisAsUsed(node);
+      },
+
+      // Mark `ref` usage
+      JSXAttribute(node) {
+        const name = getText(context, node.name);
+        if (name !== 'ref') {
+          return;
+        }
+        markRefAsUsed(node);
+      },
+
+      // Mark `render` that do not return some JSX
+      ReturnStatement(node) {
+        let blockNode;
+        let scope = getScope(context, node);
+        while (scope) {
+          blockNode = scope.block && scope.block.parent;
+          if (blockNode && (blockNode.type === 'MethodDefinition' || blockNode.type === 'Property')) {
+            break;
+          }
+          scope = scope.upper;
+        }
+        const isRender = blockNode
+          && blockNode.key
+          && blockNode.key.name === 'render';
+        const allowNull = testReactVersion(context, '>= 15.0.0'); // Stateless components can return null since React 15
+        const isReturningJSX = utils.isReturningJSX(node, !allowNull);
+        const isReturningNull = node.argument && (node.argument.value === null || node.argument.value === false);
+        if (
+          !isRender
+          || (allowNull && (isReturningJSX || isReturningNull))
+          || (!allowNull && isReturningJSX)
+        ) {
+          return;
+        }
+        markReturnAsInvalid(node);
+      },
+
+      'Program:exit'() {
+        const list = components.list();
+        values(list)
+          .filter((component) => (
+            !hasOtherProperties(component.node)
+            && !component.useThis
+            && !component.useRef
+            && !component.invalidReturn
+            && !component.hasChildContextTypes
+            && !component.useDecorators
+            && !component.hasSCU
+            && (
+              componentUtil.isES5Component(component.node, context)
+              || componentUtil.isES6Component(component.node, context)
+            )
+          ))
+          .forEach((component) => {
+            report(context, messages.componentShouldBePure, 'componentShouldBePure', {
+              node: component.node,
+            });
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"prop-types.d.ts","sourceRoot":"","sources":["prop-types.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,225 @@
+/**
+ * @fileoverview Prevent missing props validation in a React component definition
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+// As for exceptions for props.children or props.className (and alike) look at
+// https://github.com/jsx-eslint/eslint-plugin-react/issues/7
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  missingPropType: '\'{{name}}\' is missing in props validation',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow missing props validation in a React component definition',
+      category: 'Best Practices',
+      recommended: true,
+      url: docsUrl('prop-types'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignore: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        customValidators: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        skipUndeclared: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    const configuration = context.options[0] || {};
+    const ignored = configuration.ignore || [];
+    const skipUndeclared = configuration.skipUndeclared || false;
+
+    /**
+     * Checks if the prop is ignored
+     * @param {string} name Name of the prop to check.
+     * @returns {boolean} True if the prop is ignored, false if not.
+     */
+    function isIgnored(name) {
+      return ignored.indexOf(name) !== -1;
+    }
+
+    /**
+     * Checks if the component must be validated
+     * @param {Object} component The component to process
+     * @returns {boolean} True if the component must be validated, false if not.
+     */
+    function mustBeValidated(component) {
+      const isSkippedByConfig = skipUndeclared && typeof component.declaredPropTypes === 'undefined';
+      return !!(
+        component
+        && component.usedPropTypes
+        && !component.ignorePropsValidation
+        && !isSkippedByConfig
+      );
+    }
+
+    /**
+     * Internal: Checks if the prop is declared
+     * @param {Object} declaredPropTypes Description of propTypes declared in the current component
+     * @param {string[]} keyList Dot separated name of the prop to check.
+     * @returns {boolean} True if the prop is declared, false if not.
+     */
+    function internalIsDeclaredInComponent(declaredPropTypes, keyList) {
+      for (let i = 0, j = keyList.length; i < j; i++) {
+        const key = keyList[i];
+        const propType = (
+          declaredPropTypes && (
+            // Check if this key is declared
+            (declaredPropTypes[key] // If not, check if this type accepts any key
+            || declaredPropTypes.__ANY_KEY__) // eslint-disable-line no-underscore-dangle
+          )
+        );
+
+        if (!propType) {
+          // If it's a computed property, we can't make any further analysis, but is valid
+          return key === '__COMPUTED_PROP__';
+        }
+        if (typeof propType === 'object' && !propType.type) {
+          return true;
+        }
+        // Consider every children as declared
+        if (propType.children === true || propType.containsUnresolvedSpread || propType.containsIndexers) {
+          return true;
+        }
+        if (propType.acceptedProperties) {
+          return key in propType.acceptedProperties;
+        }
+        if (propType.type === 'union') {
+          // If we fall in this case, we know there is at least one complex type in the union
+          if (i + 1 >= j) {
+            // this is the last key, accept everything
+            return true;
+          }
+          // non trivial, check all of them
+          const unionTypes = propType.children;
+          const unionPropType = {};
+          for (let k = 0, z = unionTypes.length; k < z; k++) {
+            unionPropType[key] = unionTypes[k];
+            const isValid = internalIsDeclaredInComponent(
+              unionPropType,
+              keyList.slice(i)
+            );
+            if (isValid) {
+              return true;
+            }
+          }
+
+          // every possible union were invalid
+          return false;
+        }
+        declaredPropTypes = propType.children;
+      }
+      return true;
+    }
+
+    /**
+     * Checks if the prop is declared
+     * @param {ASTNode} node The AST node being checked.
+     * @param {string[]} names List of names of the prop to check.
+     * @returns {boolean} True if the prop is declared, false if not.
+     */
+    function isDeclaredInComponent(node, names) {
+      while (node) {
+        const component = components.get(node);
+
+        const isDeclared = component && component.confidence >= 2
+          && internalIsDeclaredInComponent(component.declaredPropTypes || {}, names);
+
+        if (isDeclared) {
+          return true;
+        }
+
+        node = node.parent;
+      }
+      return false;
+    }
+
+    /**
+     * Reports undeclared proptypes for a given component
+     * @param {Object} component The component to process
+     */
+    function reportUndeclaredPropTypes(component) {
+      const undeclareds = component.usedPropTypes.filter((propType) => (
+        propType.node
+        && !isIgnored(propType.allNames[0])
+        && !isDeclaredInComponent(component.node, propType.allNames)
+      ));
+      undeclareds.forEach((propType) => {
+        report(context, messages.missingPropType, 'missingPropType', {
+          node: propType.node,
+          data: {
+            name: propType.allNames.join('.').replace(/\.__COMPUTED_PROP__/g, '[]'),
+          },
+        });
+      });
+    }
+
+    /**
+     * @param {Object} component The current component to process
+     * @param {Array} list The all components to process
+     * @returns {boolean} True if the component is nested False if not.
+     */
+    function checkNestedComponent(component, list) {
+      const componentIsMemo = component.node.callee && component.node.callee.name === 'memo';
+      const argumentIsForwardRef = component.node.arguments && component.node.arguments[0].callee && component.node.arguments[0].callee.name === 'forwardRef';
+      if (componentIsMemo && argumentIsForwardRef) {
+        const forwardComponent = list.find(
+          (innerComponent) => (
+            innerComponent.node.range[0] === component.node.arguments[0].range[0]
+            && innerComponent.node.range[0] === component.node.arguments[0].range[0]
+          ));
+
+        const isValidated = mustBeValidated(forwardComponent);
+        const isIgnorePropsValidation = forwardComponent.ignorePropsValidation;
+
+        return isIgnorePropsValidation || isValidated;
+      }
+    }
+
+    return {
+      'Program:exit'() {
+        const list = components.list();
+        // Report undeclared proptypes for all classes
+        values(list)
+          .filter((component) => mustBeValidated(component))
+          .forEach((component) => {
+            if (checkNestedComponent(component, values(list))) return;
+            reportUndeclaredPropTypes(component);
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=react-in-jsx-scope.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"react-in-jsx-scope.d.ts","sourceRoot":"","sources":["react-in-jsx-scope.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/react-in-jsx-scope.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Prevent missing React when using JSX
+ * @author Glen Mailer
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const pragmaUtil = require('../util/pragma');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+const messages = {
+  notInScope: '\'{{name}}\' must be in scope when using JSX',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow missing React when using JSX',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('react-in-jsx-scope'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create(context) {
+    const pragma = pragmaUtil.getFromContext(context);
+
+    function checkIfReactIsInScope(node) {
+      if (variableUtil.getVariableFromContext(context, node, pragma)) {
+        return;
+      }
+      report(context, messages.notInScope, 'notInScope', {
+        node,
+        data: {
+          name: pragma,
+        },
+      });
+    }
+
+    return {
+      JSXOpeningElement: checkIfReactIsInScope,
+      JSXOpeningFragment: checkIfReactIsInScope,
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=require-default-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"require-default-props.d.ts","sourceRoot":"","sources":["require-default-props.js"],"names":[],"mappings":"wBAiCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,209 @@
+/**
+ * @fileOverview Enforce a defaultProps definition for every prop that is not a required prop.
+ * @author Vitor Balocco
+ */
+
+'use strict';
+
+const entries = require('object.entries');
+const values = require('object.values');
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noDefaultWithRequired: 'propType "{{name}}" is required and should not have a defaultProps declaration.',
+  shouldHaveDefault: 'propType "{{name}}" is not required, but has no corresponding defaultProps declaration.',
+  noDefaultPropsWithFunction: 'Don’t use defaultProps with function components.',
+  shouldAssignObjectDefault: 'propType "{{name}}" is not required, but has no corresponding default argument value.',
+  destructureInSignature: 'Must destructure props in the function signature to initialize an optional prop.',
+};
+
+function isPropWithNoDefaulVal(prop) {
+  if (prop.type === 'RestElement' || prop.type === 'ExperimentalRestProperty') {
+    return false;
+  }
+  return prop.value.type !== 'AssignmentPattern';
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce a defaultProps definition for every prop that is not a required prop',
+      category: 'Best Practices',
+      url: docsUrl('require-default-props'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        forbidDefaultForRequired: {
+          type: 'boolean',
+        },
+        classes: {
+          enum: ['defaultProps', 'ignore'],
+        },
+        functions: {
+          enum: ['defaultArguments', 'defaultProps', 'ignore'],
+        },
+        /**
+         * @deprecated
+         */
+        ignoreFunctionalComponents: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    const configuration = context.options[0] || {};
+    const forbidDefaultForRequired = configuration.forbidDefaultForRequired || false;
+    const classes = configuration.classes || 'defaultProps';
+    /**
+     * @todo
+     * - Remove ignoreFunctionalComponents
+     * - Change default to 'defaultArguments'
+     */
+    const functions = configuration.ignoreFunctionalComponents
+      ? 'ignore'
+      : configuration.functions || 'defaultProps';
+
+    /**
+     * Reports all propTypes passed in that don't have a defaultProps counterpart.
+     * @param  {Object[]} propTypes    List of propTypes to check.
+     * @param  {Object}   defaultProps Object of defaultProps to check. Keys are the props names.
+     * @return {void}
+     */
+    function reportPropTypesWithoutDefault(propTypes, defaultProps) {
+      entries(propTypes).forEach((propType) => {
+        const propName = propType[0];
+        const prop = propType[1];
+
+        if (!prop.node) {
+          return;
+        }
+        if (prop.isRequired) {
+          if (forbidDefaultForRequired && defaultProps[propName]) {
+            report(context, messages.noDefaultWithRequired, 'noDefaultWithRequired', {
+              node: prop.node,
+              data: { name: propName },
+            });
+          }
+          return;
+        }
+
+        if (defaultProps[propName]) {
+          return;
+        }
+
+        report(context, messages.shouldHaveDefault, 'shouldHaveDefault', {
+          node: prop.node,
+          data: { name: propName },
+        });
+      });
+    }
+
+    /**
+     * If functions option is 'defaultArguments', reports defaultProps is used and all params that doesn't initialized.
+     * @param {Object} componentNode Node of component.
+     * @param {Object[]} declaredPropTypes List of propTypes to check `isRequired`.
+     * @param {Object} defaultProps Object of defaultProps to check used.
+     */
+    function reportFunctionComponent(componentNode, declaredPropTypes, defaultProps) {
+      if (defaultProps) {
+        report(context, messages.noDefaultPropsWithFunction, 'noDefaultPropsWithFunction', {
+          node: componentNode,
+        });
+      }
+
+      const props = componentNode.params[0];
+      const propTypes = declaredPropTypes;
+
+      if (!props) {
+        return;
+      }
+
+      if (props.type === 'Identifier') {
+        const hasOptionalProp = values(propTypes).some((propType) => !propType.isRequired);
+        if (hasOptionalProp) {
+          report(context, messages.destructureInSignature, 'destructureInSignature', {
+            node: props,
+          });
+        }
+      } else if (props.type === 'ObjectPattern') {
+        // Filter required props with default value and report error
+        props.properties.filter((prop) => {
+          const propName = prop && prop.key && prop.key.name;
+          const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
+          return propTypes[propName] && isPropRequired && !isPropWithNoDefaulVal(prop);
+        }).forEach((prop) => {
+          report(context, messages.noDefaultWithRequired, 'noDefaultWithRequired', {
+            node: prop,
+            data: { name: prop.key.name },
+          });
+        });
+
+        // Filter non required props with no default value and report error
+        props.properties.filter((prop) => {
+          const propName = prop && prop.key && prop.key.name;
+          const isPropRequired = propTypes[propName] && propTypes[propName].isRequired;
+          return propTypes[propName] && !isPropRequired && isPropWithNoDefaulVal(prop);
+        }).forEach((prop) => {
+          report(context, messages.shouldAssignObjectDefault, 'shouldAssignObjectDefault', {
+            node: prop,
+            data: { name: prop.key.name },
+          });
+        });
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public API
+    // --------------------------------------------------------------------------
+
+    return {
+      'Program:exit'() {
+        const list = components.list();
+
+        values(list).filter((component) => {
+          if (functions === 'ignore' && astUtil.isFunctionLike(component.node)) {
+            return false;
+          }
+          if (classes === 'ignore' && astUtil.isClass(component.node)) {
+            return false;
+          }
+
+          // If this defaultProps is "unresolved", then we should ignore this component and not report
+          // any errors for it, to avoid false-positives with e.g. external defaultProps declarations or spread operators.
+          if (component.defaultProps === 'unresolved') {
+            return false;
+          }
+          return component.declaredPropTypes !== undefined;
+        }).forEach((component) => {
+          if (functions === 'defaultArguments' && astUtil.isFunctionLike(component.node)) {
+            reportFunctionComponent(
+              component.node,
+              component.declaredPropTypes,
+              component.defaultProps
+            );
+          } else {
+            reportPropTypesWithoutDefault(
+              component.declaredPropTypes,
+              component.defaultProps || {}
+            );
+          }
+        });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=require-optimization.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"require-optimization.d.ts","sourceRoot":"","sources":["require-optimization.js"],"names":[],"mappings":"wBAmBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-optimization.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,240 @@
+/**
+ * @fileoverview Enforce React components to have a shouldComponentUpdate method
+ * @author Evgueni Naverniouk
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getScope = require('../util/eslint').getScope;
+
+const messages = {
+  noShouldComponentUpdate: 'Component is not optimized. Please add a shouldComponentUpdate method.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce React components to have a shouldComponentUpdate method',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('require-optimization'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        allowDecorators: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    const configuration = context.options[0] || {};
+    const allowDecorators = configuration.allowDecorators || [];
+
+    /**
+     * Checks to see if our component is decorated by PureRenderMixin via reactMixin
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if node is decorated with a PureRenderMixin, false if not.
+     */
+    function hasPureRenderDecorator(node) {
+      if (node.decorators && node.decorators.length) {
+        for (let i = 0, l = node.decorators.length; i < l; i++) {
+          if (
+            node.decorators[i].expression
+            && node.decorators[i].expression.callee
+            && node.decorators[i].expression.callee.object
+            && node.decorators[i].expression.callee.object.name === 'reactMixin'
+            && node.decorators[i].expression.callee.property
+            && node.decorators[i].expression.callee.property.name === 'decorate'
+            && node.decorators[i].expression.arguments
+            && node.decorators[i].expression.arguments.length
+            && node.decorators[i].expression.arguments[0].name === 'PureRenderMixin'
+          ) {
+            return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Checks to see if our component is custom decorated
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if node is decorated name with a custom decorated, false if not.
+     */
+    function hasCustomDecorator(node) {
+      const allowLength = allowDecorators.length;
+
+      if (allowLength && node.decorators && node.decorators.length) {
+        for (let i = 0; i < allowLength; i++) {
+          for (let j = 0, l = node.decorators.length; j < l; j++) {
+            const expression = node.decorators[j].expression;
+            if (
+              expression
+              && expression.name === allowDecorators[i]
+            ) {
+              return true;
+            }
+          }
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Checks if we are declaring a shouldComponentUpdate method
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if we are declaring a shouldComponentUpdate method, false if not.
+     */
+    function isSCUDeclared(node) {
+      return !!node && node.name === 'shouldComponentUpdate';
+    }
+
+    /**
+     * Checks if we are declaring a PureRenderMixin mixin
+     * @param {ASTNode} node The AST node being checked.
+     * @returns {boolean} True if we are declaring a PureRenderMixin method, false if not.
+     */
+    function isPureRenderDeclared(node) {
+      let hasPR = false;
+      if (node.value && node.value.elements) {
+        for (let i = 0, l = node.value.elements.length; i < l; i++) {
+          if (node.value.elements[i] && node.value.elements[i].name === 'PureRenderMixin') {
+            hasPR = true;
+            break;
+          }
+        }
+      }
+
+      return (
+        !!node
+        && node.key.name === 'mixins'
+        && hasPR
+      );
+    }
+
+    /**
+     * Mark shouldComponentUpdate as declared
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markSCUAsDeclared(node) {
+      components.set(node, {
+        hasSCU: true,
+      });
+    }
+
+    /**
+     * Reports missing optimization for a given component
+     * @param {Object} component The component to process
+     */
+    function reportMissingOptimization(component) {
+      report(context, messages.noShouldComponentUpdate, 'noShouldComponentUpdate', {
+        node: component.node,
+      });
+    }
+
+    /**
+     * Checks if we are declaring function in class
+     * @param {ASTNode} node
+     * @returns {boolean} True if we are declaring function in class, false if not.
+     */
+    function isFunctionInClass(node) {
+      let blockNode;
+      let scope = getScope(context, node);
+      while (scope) {
+        blockNode = scope.block;
+        if (blockNode && blockNode.type === 'ClassDeclaration') {
+          return true;
+        }
+        scope = scope.upper;
+      }
+
+      return false;
+    }
+
+    return {
+      ArrowFunctionExpression(node) {
+        // Skip if the function is declared in the class
+        if (isFunctionInClass(node)) {
+          return;
+        }
+        // Stateless Functional Components cannot be optimized (yet)
+        markSCUAsDeclared(node);
+      },
+
+      ClassDeclaration(node) {
+        if (!(
+          hasPureRenderDecorator(node)
+          || hasCustomDecorator(node)
+          || componentUtil.isPureComponent(node, context)
+        )) {
+          return;
+        }
+        markSCUAsDeclared(node);
+      },
+
+      FunctionDeclaration(node) {
+        // Skip if the function is declared in the class
+        if (isFunctionInClass(node)) {
+          return;
+        }
+        // Stateless Functional Components cannot be optimized (yet)
+        markSCUAsDeclared(node);
+      },
+
+      FunctionExpression(node) {
+        // Skip if the function is declared in the class
+        if (isFunctionInClass(node)) {
+          return;
+        }
+        // Stateless Functional Components cannot be optimized (yet)
+        markSCUAsDeclared(node);
+      },
+
+      MethodDefinition(node) {
+        if (!isSCUDeclared(node.key)) {
+          return;
+        }
+        markSCUAsDeclared(node);
+      },
+
+      ObjectExpression(node) {
+        // Search for the shouldComponentUpdate declaration
+        const found = node.properties.some((property) => (
+          property.key
+          && (isSCUDeclared(property.key) || isPureRenderDeclared(property))
+        ));
+        if (found) {
+          markSCUAsDeclared(node);
+        }
+      },
+
+      'Program:exit'() {
+        // Report missing shouldComponentUpdate for all components
+        values(components.list())
+          .filter((component) => !component.hasSCU)
+          .forEach((component) => {
+            reportMissingOptimization(component);
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=require-render-return.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"require-render-return.d.ts","sourceRoot":"","sources":["require-render-return.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/require-render-return.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,106 @@
+/**
+ * @fileoverview Enforce ES5 or ES6 class for returning value in render function.
+ * @author Mark Orel
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const Components = require('../util/Components');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const getAncestors = require('../util/eslint').getAncestors;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  noRenderReturn: 'Your render method should have a return statement',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce ES5 or ES6 class for returning value in render function',
+      category: 'Possible Errors',
+      recommended: true,
+      url: docsUrl('require-render-return'),
+    },
+
+    messages,
+
+    schema: [],
+  },
+
+  create: Components.detect((context, components) => {
+    /**
+     * Mark a return statement as present
+     * @param {ASTNode} node The AST node being checked.
+     */
+    function markReturnStatementPresent(node) {
+      components.set(node, {
+        hasReturnStatement: true,
+      });
+    }
+
+    /**
+     * Find render method in a given AST node
+     * @param {ASTNode} node The component to find render method.
+     * @returns {ASTNode} Method node if found, undefined if not.
+     */
+    function findRenderMethod(node) {
+      const properties = astUtil.getComponentProperties(node);
+      return properties
+        .filter((property) => astUtil.getPropertyName(property) === 'render' && property.value)
+        .find((property) => astUtil.isFunctionLikeExpression(property.value));
+    }
+
+    return {
+      ReturnStatement(node) {
+        const ancestors = getAncestors(context, node).reverse();
+        let depth = 0;
+        ancestors.forEach((ancestor) => {
+          if (/Function(Expression|Declaration)$/.test(ancestor.type)) {
+            depth += 1;
+          }
+          if (
+            /(MethodDefinition|Property|ClassProperty|PropertyDefinition)$/.test(ancestor.type)
+            && astUtil.getPropertyName(ancestor) === 'render'
+            && depth <= 1
+          ) {
+            markReturnStatementPresent(node);
+          }
+        });
+      },
+
+      ArrowFunctionExpression(node) {
+        if (node.expression === false || astUtil.getPropertyName(node.parent) !== 'render') {
+          return;
+        }
+        markReturnStatementPresent(node);
+      },
+
+      'Program:exit'() {
+        values(components.list())
+          .filter((component) => (
+            findRenderMethod(component.node)
+            && !component.hasReturnStatement
+            && (
+              componentUtil.isES5Component(component.node, context)
+              || componentUtil.isES6Component(component.node, context)
+            )
+          ))
+          .forEach((component) => {
+            report(context, messages.noRenderReturn, 'noRenderReturn', {
+              node: findRenderMethod(component.node),
+            });
+          });
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=self-closing-comp.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"self-closing-comp.d.ts","sourceRoot":"","sources":["self-closing-comp.js"],"names":[],"mappings":"wBA4CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/self-closing-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Prevent extra closing tags for components without children
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const docsUrl = require('../util/docsUrl');
+const jsxUtil = require('../util/jsx');
+const report = require('../util/report');
+
+const optionDefaults = { component: true, html: true };
+
+function isComponent(node) {
+  return (
+    node.name
+    && (node.name.type === 'JSXIdentifier' || node.name.type === 'JSXMemberExpression')
+    && !jsxUtil.isDOMComponent(node)
+  );
+}
+
+function childrenIsEmpty(node) {
+  return node.parent.children.length === 0;
+}
+
+function childrenIsMultilineSpaces(node) {
+  const childrens = node.parent.children;
+
+  return (
+    childrens.length === 1
+    && (childrens[0].type === 'Literal' || childrens[0].type === 'JSXText')
+    && childrens[0].value.indexOf('\n') !== -1
+    && childrens[0].value.replace(/(?!\xA0)\s/g, '') === ''
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  notSelfClosing: 'Empty components are self-closing',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow extra closing tags for components without children',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('self-closing-comp'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        component: {
+          default: optionDefaults.component,
+          type: 'boolean',
+        },
+        html: {
+          default: optionDefaults.html,
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    function isShouldBeSelfClosed(node) {
+      const configuration = Object.assign({}, optionDefaults, context.options[0]);
+      return (
+        (configuration.component && isComponent(node))
+        || (configuration.html && jsxUtil.isDOMComponent(node))
+      ) && !node.selfClosing && (childrenIsEmpty(node) || childrenIsMultilineSpaces(node));
+    }
+
+    return {
+      JSXOpeningElement(node) {
+        if (!isShouldBeSelfClosed(node)) {
+          return;
+        }
+        report(context, messages.notSelfClosing, 'notSelfClosing', {
+          node,
+          fix(fixer) {
+            // Represents the last character of the JSXOpeningElement, the '>' character
+            const openingElementEnding = node.range[1] - 1;
+            // Represents the last character of the JSXClosingElement, the '>' character
+            const closingElementEnding = node.parent.closingElement.range[1];
+
+            // Replace />.*<\/.*>/ with '/>'
+            const range = [openingElementEnding, closingElementEnding];
+            return fixer.replaceTextRange(range, ' />');
+          },
+        });
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=sort-comp.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"sort-comp.d.ts","sourceRoot":"","sources":["sort-comp.js"],"names":[],"mappings":"wBAwFW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-comp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,449 @@
+/**
+ * @fileoverview Enforce component methods order
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const has = require('hasown');
+const entries = require('object.entries');
+const values = require('object.values');
+const arrayIncludes = require('array-includes');
+
+const Components = require('../util/Components');
+const astUtil = require('../util/ast');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+const defaultConfig = {
+  order: [
+    'static-methods',
+    'lifecycle',
+    'everything-else',
+    'render',
+  ],
+  groups: {
+    lifecycle: [
+      'displayName',
+      'propTypes',
+      'contextTypes',
+      'childContextTypes',
+      'mixins',
+      'statics',
+      'defaultProps',
+      'constructor',
+      'getDefaultProps',
+      'state',
+      'getInitialState',
+      'getChildContext',
+      'getDerivedStateFromProps',
+      'componentWillMount',
+      'UNSAFE_componentWillMount',
+      'componentDidMount',
+      'componentWillReceiveProps',
+      'UNSAFE_componentWillReceiveProps',
+      'shouldComponentUpdate',
+      'componentWillUpdate',
+      'UNSAFE_componentWillUpdate',
+      'getSnapshotBeforeUpdate',
+      'componentDidUpdate',
+      'componentDidCatch',
+      'componentWillUnmount',
+    ],
+  },
+};
+
+/**
+ * Get the methods order from the default config and the user config
+ * @param {Object} userConfig The user configuration.
+ * @returns {Array} Methods order
+ */
+function getMethodsOrder(userConfig) {
+  userConfig = userConfig || {};
+
+  const groups = Object.assign({}, defaultConfig.groups, userConfig.groups);
+  const order = userConfig.order || defaultConfig.order;
+
+  let config = [];
+  let entry;
+  for (let i = 0, j = order.length; i < j; i++) {
+    entry = order[i];
+    if (has(groups, entry)) {
+      config = config.concat(groups[entry]);
+    } else {
+      config.push(entry);
+    }
+  }
+
+  return config;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  unsortedProps: '{{propA}} should be placed {{position}} {{propB}}',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce component methods order',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('sort-comp'),
+    },
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        order: {
+          type: 'array',
+          items: {
+            type: 'string',
+          },
+        },
+        groups: {
+          type: 'object',
+          patternProperties: {
+            '^.*$': {
+              type: 'array',
+              items: {
+                type: 'string',
+              },
+            },
+          },
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create: Components.detect((context, components) => {
+    /** @satisfies {Record<string, { node: ASTNode, score: number, closest: { distance: number, ref: { node: null | ASTNode, index: number } } }>} */
+    const errors = {};
+    const methodsOrder = getMethodsOrder(context.options[0]);
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    const regExpRegExp = /\/(.*)\/([gimsuy]*)/;
+
+    /**
+     * Get indexes of the matching patterns in methods order configuration
+     * @param {Object} method - Method metadata.
+     * @returns {Array} The matching patterns indexes. Return [Infinity] if there is no match.
+     */
+    function getRefPropIndexes(method) {
+      const methodGroupIndexes = [];
+
+      methodsOrder.forEach((currentGroup, groupIndex) => {
+        if (currentGroup === 'getters') {
+          if (method.getter) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'setters') {
+          if (method.setter) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'type-annotations') {
+          if (method.typeAnnotation) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'static-variables') {
+          if (method.staticVariable) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'static-methods') {
+          if (method.staticMethod) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'instance-variables') {
+          if (method.instanceVariable) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (currentGroup === 'instance-methods') {
+          if (method.instanceMethod) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else if (arrayIncludes([
+          'displayName',
+          'propTypes',
+          'contextTypes',
+          'childContextTypes',
+          'mixins',
+          'statics',
+          'defaultProps',
+          'constructor',
+          'getDefaultProps',
+          'state',
+          'getInitialState',
+          'getChildContext',
+          'getDerivedStateFromProps',
+          'componentWillMount',
+          'UNSAFE_componentWillMount',
+          'componentDidMount',
+          'componentWillReceiveProps',
+          'UNSAFE_componentWillReceiveProps',
+          'shouldComponentUpdate',
+          'componentWillUpdate',
+          'UNSAFE_componentWillUpdate',
+          'getSnapshotBeforeUpdate',
+          'componentDidUpdate',
+          'componentDidCatch',
+          'componentWillUnmount',
+          'render',
+        ], currentGroup)) {
+          if (currentGroup === method.name) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        } else {
+          // Is the group a regex?
+          const isRegExp = currentGroup.match(regExpRegExp);
+          if (isRegExp) {
+            const isMatching = new RegExp(isRegExp[1], isRegExp[2]).test(method.name);
+            if (isMatching) {
+              methodGroupIndexes.push(groupIndex);
+            }
+          } else if (currentGroup === method.name) {
+            methodGroupIndexes.push(groupIndex);
+          }
+        }
+      });
+
+      // No matching pattern, return 'everything-else' index
+      if (methodGroupIndexes.length === 0) {
+        const everythingElseIndex = methodsOrder.indexOf('everything-else');
+
+        if (everythingElseIndex !== -1) {
+          methodGroupIndexes.push(everythingElseIndex);
+        } else {
+          // No matching pattern and no 'everything-else' group
+          methodGroupIndexes.push(Infinity);
+        }
+      }
+
+      return methodGroupIndexes;
+    }
+
+    /**
+     * Get properties name
+     * @param {Object} node - Property.
+     * @returns {string} Property name.
+     */
+    function getPropertyName(node) {
+      if (node.kind === 'get') {
+        return 'getter functions';
+      }
+
+      if (node.kind === 'set') {
+        return 'setter functions';
+      }
+
+      return astUtil.getPropertyName(node);
+    }
+
+    /**
+     * Store a new error in the error list
+     * @param {Object} propA - Mispositioned property.
+     * @param {Object} propB - Reference property.
+     */
+    function storeError(propA, propB) {
+      // Initialize the error object if needed
+      if (!errors[propA.index]) {
+        errors[propA.index] = {
+          node: propA.node,
+          score: 0,
+          closest: {
+            distance: Infinity,
+            ref: {
+              node: null,
+              index: 0,
+            },
+          },
+        };
+      }
+      // Increment the prop score
+      errors[propA.index].score += 1;
+      // Stop here if we already have pushed another node at this position
+      if (getPropertyName(errors[propA.index].node) !== getPropertyName(propA.node)) {
+        return;
+      }
+      // Stop here if we already have a closer reference
+      if (Math.abs(propA.index - propB.index) > errors[propA.index].closest.distance) {
+        return;
+      }
+      // Update the closest reference
+      errors[propA.index].closest.distance = Math.abs(propA.index - propB.index);
+      errors[propA.index].closest.ref.node = propB.node;
+      errors[propA.index].closest.ref.index = propB.index;
+    }
+
+    /**
+     * Dedupe errors, only keep the ones with the highest score and delete the others
+     */
+    function dedupeErrors() {
+      entries(errors).forEach((entry) => {
+        const i = entry[0];
+        const error = entry[1];
+
+        const index = error.closest.ref.index;
+        if (errors[index]) {
+          if (error.score > errors[index].score) {
+            delete errors[index];
+          } else {
+            delete errors[i];
+          }
+        }
+      });
+    }
+
+    /**
+     * Report errors
+     */
+    function reportErrors() {
+      dedupeErrors();
+
+      entries(errors).forEach((entry) => {
+        const nodeA = entry[1].node;
+        const nodeB = entry[1].closest.ref.node;
+        const indexA = entry[0];
+        const indexB = entry[1].closest.ref.index;
+
+        report(context, messages.unsortedProps, 'unsortedProps', {
+          node: nodeA,
+          data: {
+            propA: getPropertyName(nodeA),
+            propB: getPropertyName(nodeB),
+            position: indexA < indexB ? 'before' : 'after',
+          },
+        });
+      });
+    }
+
+    /**
+     * Compare two properties and find out if they are in the right order
+     * @param {Array} propertiesInfos Array containing all the properties metadata.
+     * @param {Object} propA First property name and metadata
+     * @param {Object} propB Second property name.
+     * @returns {Object} Object containing a correct true/false flag and the correct indexes for the two properties.
+     */
+    function comparePropsOrder(propertiesInfos, propA, propB) {
+      let i;
+      let j;
+      let k;
+      let l;
+      let refIndexA;
+      let refIndexB;
+
+      // Get references indexes (the correct position) for given properties
+      const refIndexesA = getRefPropIndexes(propA);
+      const refIndexesB = getRefPropIndexes(propB);
+
+      // Get current indexes for given properties
+      const classIndexA = propertiesInfos.indexOf(propA);
+      const classIndexB = propertiesInfos.indexOf(propB);
+
+      // Loop around the references indexes for the 1st property
+      for (i = 0, j = refIndexesA.length; i < j; i++) {
+        refIndexA = refIndexesA[i];
+
+        // Loop around the properties for the 2nd property (for comparison)
+        for (k = 0, l = refIndexesB.length; k < l; k++) {
+          refIndexB = refIndexesB[k];
+
+          if (
+            // Comparing the same properties
+            refIndexA === refIndexB
+            // 1st property is placed before the 2nd one in reference and in current component
+            || ((refIndexA < refIndexB) && (classIndexA < classIndexB))
+            // 1st property is placed after the 2nd one in reference and in current component
+            || ((refIndexA > refIndexB) && (classIndexA > classIndexB))
+          ) {
+            return {
+              correct: true,
+              indexA: classIndexA,
+              indexB: classIndexB,
+            };
+          }
+        }
+      }
+
+      // We did not find any correct match between reference and current component
+      return {
+        correct: false,
+        indexA: refIndexA,
+        indexB: refIndexB,
+      };
+    }
+
+    /**
+     * Check properties order from a properties list and store the eventual errors
+     * @param {Array} properties Array containing all the properties.
+     */
+    function checkPropsOrder(properties) {
+      const propertiesInfos = properties.map((node) => ({
+        name: getPropertyName(node),
+        getter: node.kind === 'get',
+        setter: node.kind === 'set',
+        staticVariable: node.static
+          && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')
+          && (!node.value || !astUtil.isFunctionLikeExpression(node.value)),
+        staticMethod: node.static
+          && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition' || node.type === 'MethodDefinition')
+          && node.value
+          && (astUtil.isFunctionLikeExpression(node.value)),
+        instanceVariable: !node.static
+          && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')
+          && (!node.value || !astUtil.isFunctionLikeExpression(node.value)),
+        instanceMethod: !node.static
+          && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')
+          && node.value
+          && (astUtil.isFunctionLikeExpression(node.value)),
+        typeAnnotation: !!node.typeAnnotation && node.value === null,
+      }));
+
+      // Loop around the properties
+      propertiesInfos.forEach((propA, i) => {
+        // Loop around the properties a second time (for comparison)
+        propertiesInfos.forEach((propB, k) => {
+          if (i === k) {
+            return;
+          }
+
+          // Compare the properties order
+          const order = comparePropsOrder(propertiesInfos, propA, propB);
+
+          if (!order.correct) {
+            // Store an error if the order is incorrect
+            storeError({
+              node: properties[i],
+              index: order.indexA,
+            }, {
+              node: properties[k],
+              index: order.indexB,
+            });
+          }
+        });
+      });
+    }
+
+    return {
+      'Program:exit'() {
+        values(components.list()).forEach((component) => {
+          const properties = astUtil.getComponentProperties(component.node);
+          checkPropsOrder(properties);
+        });
+
+        reportErrors();
+      },
+    };
+  }),
+
+  defaultConfig,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=sort-default-props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"sort-default-props.d.ts","sourceRoot":"","sources":["sort-default-props.js"],"names":[],"mappings":"wBAwBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-default-props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,180 @@
+/**
+ * @fileoverview Enforce default props alphabetical sorting
+ * @author Vladimir Kattsov
+ * @deprecated
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getFirstTokens = eslintUtil.getFirstTokens;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  propsNotSorted: 'Default prop types declarations should be sorted alphabetically',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce defaultProps declarations alphabetical sorting',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('sort-default-props'),
+    },
+    // fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreCase: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const ignoreCase = configuration.ignoreCase || false;
+
+    /**
+     * Get properties name
+     * @param {Object} node - Property.
+     * @returns {string} Property name.
+     */
+    function getPropertyName(node) {
+      if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
+        return node.key.name;
+      }
+      if (node.type === 'MemberExpression') {
+        return node.property.name;
+      // Special case for class properties
+      // (babel-eslint@5 does not expose property name so we have to rely on tokens)
+      }
+      if (node.type === 'ClassProperty') {
+        const tokens = getFirstTokens(context, node, 2);
+        return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
+      }
+      return '';
+    }
+
+    /**
+     * Checks if the Identifier node passed in looks like a defaultProps declaration.
+     * @param   {ASTNode}  node The node to check. Must be an Identifier node.
+     * @returns {boolean}       `true` if the node is a defaultProps declaration, `false` if not
+     */
+    function isDefaultPropsDeclaration(node) {
+      const propName = getPropertyName(node);
+      return (propName === 'defaultProps' || propName === 'getDefaultProps');
+    }
+
+    function getKey(node) {
+      return getText(context, node.key || node.argument);
+    }
+
+    /**
+     * Find a variable by name in the current scope.
+     * @param  {ASTNode} node The node to look for.
+     * @param  {string} name Name of the variable to look for.
+     * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
+     */
+    function findVariableByName(node, name) {
+      const variable = variableUtil.getVariableFromContext(context, node, name);
+
+      if (!variable || !variable.defs[0] || !variable.defs[0].node) {
+        return null;
+      }
+
+      if (variable.defs[0].node.type === 'TypeAlias') {
+        return variable.defs[0].node.right;
+      }
+
+      return variable.defs[0].node.init;
+    }
+
+    /**
+     * Checks if defaultProps declarations are sorted
+     * @param {Array} declarations The array of AST nodes being checked.
+     * @returns {void}
+     */
+    function checkSorted(declarations) {
+      // function fix(fixer) {
+      //   return propTypesSortUtil.fixPropTypesSort(context, fixer, declarations, ignoreCase);
+      // }
+
+      declarations.reduce((prev, curr, idx, decls) => {
+        if (/Spread(?:Property|Element)$/.test(curr.type)) {
+          return decls[idx + 1];
+        }
+
+        let prevPropName = getKey(prev);
+        let currentPropName = getKey(curr);
+
+        if (ignoreCase) {
+          prevPropName = prevPropName.toLowerCase();
+          currentPropName = currentPropName.toLowerCase();
+        }
+
+        if (currentPropName < prevPropName) {
+          report(context, messages.propsNotSorted, 'propsNotSorted', {
+            node: curr,
+            // fix
+          });
+
+          return prev;
+        }
+
+        return curr;
+      }, declarations[0]);
+    }
+
+    function checkNode(node) {
+      if (!node) {
+        return;
+      }
+      if (node.type === 'ObjectExpression') {
+        checkSorted(node.properties);
+      } else if (node.type === 'Identifier') {
+        const propTypesObject = findVariableByName(node, node.name);
+        if (propTypesObject && propTypesObject.properties) {
+          checkSorted(propTypesObject.properties);
+        }
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public API
+    // --------------------------------------------------------------------------
+
+    return {
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!isDefaultPropsDeclaration(node)) {
+          return;
+        }
+
+        checkNode(node.value);
+      },
+
+      MemberExpression(node) {
+        if (!isDefaultPropsDeclaration(node)) {
+          return;
+        }
+
+        checkNode('right' in node.parent && node.parent.right);
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=sort-prop-types.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"sort-prop-types.d.ts","sourceRoot":"","sources":["sort-prop-types.js"],"names":[],"mappings":"wBAsCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/sort-prop-types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,316 @@
+/**
+ * @fileoverview Enforce propTypes declarations alphabetical sorting
+ */
+
+'use strict';
+
+const astUtil = require('../util/ast');
+const variableUtil = require('../util/variable');
+const propsUtil = require('../util/props');
+const docsUrl = require('../util/docsUrl');
+const propWrapperUtil = require('../util/propWrapper');
+const propTypesSortUtil = require('../util/propTypesSort');
+const report = require('../util/report');
+const eslintUtil = require('../util/eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  requiredPropsFirst: 'Required prop types must be listed before all other prop types',
+  callbackPropsLast: 'Callback prop types must be listed after all other prop types',
+  propsNotSorted: 'Prop types declarations should be sorted alphabetically',
+};
+
+function getKey(context, node) {
+  if (node.type === 'ObjectTypeProperty') {
+    return getSourceCode(context).getFirstToken(node).value;
+  }
+  if (node.key && node.key.value) {
+    return node.key.value;
+  }
+  return getText(context, node.key || node.argument);
+}
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce propTypes declarations alphabetical sorting',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('sort-prop-types'),
+    },
+    fixable: 'code',
+
+    messages,
+
+    schema: [{
+      type: 'object',
+      properties: {
+        requiredFirst: {
+          type: 'boolean',
+        },
+        callbacksLast: {
+          type: 'boolean',
+        },
+        ignoreCase: {
+          type: 'boolean',
+        },
+        // Whether alphabetical sorting should be enforced
+        noSortAlphabetically: {
+          type: 'boolean',
+        },
+        sortShapeProp: {
+          type: 'boolean',
+        },
+        checkTypes: {
+          type: 'boolean',
+        },
+      },
+      additionalProperties: false,
+    }],
+  },
+
+  create(context) {
+    const configuration = context.options[0] || {};
+    const requiredFirst = configuration.requiredFirst || false;
+    const callbacksLast = configuration.callbacksLast || false;
+    const ignoreCase = configuration.ignoreCase || false;
+    const noSortAlphabetically = configuration.noSortAlphabetically || false;
+    const sortShapeProp = configuration.sortShapeProp || false;
+    const checkTypes = configuration.checkTypes || false;
+
+    const typeAnnotations = new Map();
+
+    /**
+     * Checks if propTypes declarations are sorted
+     * @param {Array} declarations The array of AST nodes being checked.
+     * @returns {void}
+     */
+    function checkSorted(declarations) {
+      // Declarations will be `undefined` if the `shape` is not a literal. For
+      // example, if it is a propType imported from another file.
+      if (!declarations) {
+        return;
+      }
+
+      function fix(fixer) {
+        return propTypesSortUtil.fixPropTypesSort(
+          context,
+          fixer,
+          declarations,
+          ignoreCase,
+          requiredFirst,
+          callbacksLast,
+          noSortAlphabetically,
+          sortShapeProp,
+          checkTypes
+        );
+      }
+
+      const callbackPropsLastSeen = new WeakSet();
+      const requiredPropsFirstSeen = new WeakSet();
+      const propsNotSortedSeen = new WeakSet();
+
+      declarations.reduce((prev, curr, idx, decls) => {
+        if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
+          return decls[idx + 1];
+        }
+
+        let prevPropName = getKey(context, prev);
+        let currentPropName = getKey(context, curr);
+        const previousIsRequired = propTypesSortUtil.isRequiredProp(prev);
+        const currentIsRequired = propTypesSortUtil.isRequiredProp(curr);
+        const previousIsCallback = propTypesSortUtil.isCallbackPropName(prevPropName);
+        const currentIsCallback = propTypesSortUtil.isCallbackPropName(currentPropName);
+
+        if (ignoreCase) {
+          prevPropName = String(prevPropName).toLowerCase();
+          currentPropName = String(currentPropName).toLowerCase();
+        }
+
+        if (requiredFirst) {
+          if (previousIsRequired && !currentIsRequired) {
+            // Transition between required and non-required. Don't compare for alphabetical.
+            return curr;
+          }
+          if (!previousIsRequired && currentIsRequired) {
+            // Encountered a non-required prop after a required prop
+            if (!requiredPropsFirstSeen.has(curr)) {
+              requiredPropsFirstSeen.add(curr);
+              report(context, messages.requiredPropsFirst, 'requiredPropsFirst', {
+                node: curr,
+                fix,
+              });
+            }
+            return curr;
+          }
+        }
+
+        if (callbacksLast) {
+          if (!previousIsCallback && currentIsCallback) {
+            // Entering the callback prop section
+            return curr;
+          }
+          if (previousIsCallback && !currentIsCallback) {
+            // Encountered a non-callback prop after a callback prop
+            if (!callbackPropsLastSeen.has(prev)) {
+              callbackPropsLastSeen.add(prev);
+              report(context, messages.callbackPropsLast, 'callbackPropsLast', {
+                node: prev,
+                fix,
+              });
+            }
+            return prev;
+          }
+        }
+
+        if (!noSortAlphabetically && currentPropName < prevPropName) {
+          if (!propsNotSortedSeen.has(curr)) {
+            propsNotSortedSeen.add(curr);
+            report(context, messages.propsNotSorted, 'propsNotSorted', {
+              node: curr,
+              fix,
+            });
+          }
+          return prev;
+        }
+
+        return curr;
+      }, declarations[0]);
+    }
+
+    function checkNode(node) {
+      if (!node) {
+        return;
+      }
+
+      if (node.type === 'ObjectExpression') {
+        checkSorted(node.properties);
+      } else if (node.type === 'Identifier') {
+        const propTypesObject = variableUtil.findVariableByName(context, node, node.name);
+        if (propTypesObject && propTypesObject.properties) {
+          checkSorted(propTypesObject.properties);
+        }
+      } else if (astUtil.isCallExpression(node)) {
+        const innerNode = node.arguments && node.arguments[0];
+        if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
+          checkNode(innerNode);
+        }
+      }
+    }
+
+    function handleFunctionComponent(node) {
+      const firstArg = node.params
+        && node.params.length > 0
+        && node.params[0].typeAnnotation
+        && node.params[0].typeAnnotation.typeAnnotation;
+      if (firstArg && firstArg.type === 'TSTypeReference') {
+        const propType = typeAnnotations.get(firstArg.typeName.name)
+          && typeAnnotations.get(firstArg.typeName.name)[0];
+        if (propType && propType.members) {
+          checkSorted(propType.members);
+        }
+      } else if (firstArg && firstArg.type === 'TSTypeLiteral') {
+        if (firstArg.members) {
+          checkSorted(firstArg.members);
+        }
+      } else if (firstArg && firstArg.type === 'GenericTypeAnnotation') {
+        const propType = typeAnnotations.get(firstArg.id.name)
+          && typeAnnotations.get(firstArg.id.name)[0];
+        if (propType && propType.properties) {
+          checkSorted(propType.properties);
+        }
+      } else if (firstArg && firstArg.type === 'ObjectTypeAnnotation') {
+        if (firstArg.properties) {
+          checkSorted(firstArg.properties);
+        }
+      }
+    }
+
+    return Object.assign({
+      CallExpression(node) {
+        if (!sortShapeProp || !propTypesSortUtil.isShapeProp(node) || !(node.arguments && node.arguments[0])) {
+          return;
+        }
+
+        const firstArg = node.arguments[0];
+        if (firstArg.properties) {
+          checkSorted(firstArg.properties);
+        } else if (firstArg.type === 'Identifier') {
+          const variable = variableUtil.findVariableByName(context, node, firstArg.name);
+          if (variable && variable.properties) {
+            checkSorted(variable.properties);
+          }
+        }
+      },
+
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!propsUtil.isPropTypesDeclaration(node)) {
+          return;
+        }
+        checkNode(node.value);
+      },
+
+      MemberExpression(node) {
+        if (!propsUtil.isPropTypesDeclaration(node)) {
+          return;
+        }
+
+        checkNode(node.parent.right);
+      },
+
+      ObjectExpression(node) {
+        node.properties.forEach((property) => {
+          if (!property.key) {
+            return;
+          }
+
+          if (!propsUtil.isPropTypesDeclaration(property)) {
+            return;
+          }
+          if (property.value.type === 'ObjectExpression') {
+            checkSorted(property.value.properties);
+          }
+        });
+      },
+    }, checkTypes ? {
+      TSTypeLiteral(node) {
+        if (node && node.parent.id) {
+          const currentNode = [].concat(
+            typeAnnotations.get(node.parent.id.name) || [],
+            node
+          );
+          typeAnnotations.set(node.parent.id.name, currentNode);
+        }
+      },
+
+      TypeAlias(node) {
+        if (node.right.type === 'ObjectTypeAnnotation') {
+          const currentNode = [].concat(
+            typeAnnotations.get(node.id.name) || [],
+            node.right
+          );
+          typeAnnotations.set(node.id.name, currentNode);
+        }
+      },
+
+      TSTypeAliasDeclaration(node) {
+        if (node.typeAnnotation.type === 'TSTypeLiteral' || node.typeAnnotation.type === 'ObjectTypeAnnotation') {
+          const currentNode = [].concat(
+            typeAnnotations.get(node.id.name) || [],
+            node.typeAnnotation
+          );
+          typeAnnotations.set(node.id.name, currentNode);
+        }
+      },
+      FunctionDeclaration: handleFunctionComponent,
+      ArrowFunctionExpression: handleFunctionComponent,
+    } : null);
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=state-in-constructor.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"state-in-constructor.d.ts","sourceRoot":"","sources":["state-in-constructor.js"],"names":[],"mappings":"wBAqBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/state-in-constructor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,68 @@
+/**
+ * @fileoverview Enforce the state initialization style to be either in a constructor or with a class property
+ * @author Kanitkorn Sujautra
+ */
+
+'use strict';
+
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const docsUrl = require('../util/docsUrl');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  stateInitConstructor: 'State initialization should be in a constructor',
+  stateInitClassProp: 'State initialization should be in a class property',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce class component state initialization style',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('state-in-constructor'),
+    },
+
+    messages,
+
+    schema: [{
+      enum: ['always', 'never'],
+    }],
+  },
+
+  create(context) {
+    const option = context.options[0] || 'always';
+    return {
+      'ClassProperty, PropertyDefinition'(node) {
+        if (
+          option === 'always'
+          && !node.static
+          && node.key.name === 'state'
+          && componentUtil.getParentES6Component(context, node)
+        ) {
+          report(context, messages.stateInitConstructor, 'stateInitConstructor', {
+            node,
+          });
+        }
+      },
+      AssignmentExpression(node) {
+        if (
+          option === 'never'
+          && componentUtil.isStateMemberExpression(node.left)
+          && astUtil.inConstructor(context, node)
+          && componentUtil.getParentES6Component(context, node)
+        ) {
+          report(context, messages.stateInitClassProp, 'stateInitClassProp', {
+            node,
+          });
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=static-property-placement.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"static-property-placement.d.ts","sourceRoot":"","sources":["static-property-placement.js"],"names":[],"mappings":"wBA0DW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/static-property-placement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,194 @@
+/**
+ * @fileoverview Defines where React component static properties should be positioned.
+ * @author Daniel Mason
+ */
+
+'use strict';
+
+const fromEntries = require('object.fromentries');
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+const astUtil = require('../util/ast');
+const componentUtil = require('../util/componentUtil');
+const propsUtil = require('../util/props');
+const report = require('../util/report');
+const getScope = require('../util/eslint').getScope;
+
+// ------------------------------------------------------------------------------
+// Positioning Options
+// ------------------------------------------------------------------------------
+const STATIC_PUBLIC_FIELD = 'static public field';
+const STATIC_GETTER = 'static getter';
+const PROPERTY_ASSIGNMENT = 'property assignment';
+const POSITION_SETTINGS = [STATIC_PUBLIC_FIELD, STATIC_GETTER, PROPERTY_ASSIGNMENT];
+
+// ------------------------------------------------------------------------------
+// Rule messages
+// ------------------------------------------------------------------------------
+const ERROR_MESSAGES = {
+  [STATIC_PUBLIC_FIELD]: 'notStaticClassProp',
+  [STATIC_GETTER]: 'notGetterClassFunc',
+  [PROPERTY_ASSIGNMENT]: 'declareOutsideClass',
+};
+
+// ------------------------------------------------------------------------------
+// Properties to check
+// ------------------------------------------------------------------------------
+const propertiesToCheck = {
+  propTypes: propsUtil.isPropTypesDeclaration,
+  defaultProps: propsUtil.isDefaultPropsDeclaration,
+  childContextTypes: propsUtil.isChildContextTypesDeclaration,
+  contextTypes: propsUtil.isContextTypesDeclaration,
+  contextType: propsUtil.isContextTypeDeclaration,
+  displayName: (node) => propsUtil.isDisplayNameDeclaration(astUtil.getPropertyNameNode(node)),
+};
+
+const classProperties = Object.keys(propertiesToCheck);
+const schemaProperties = fromEntries(classProperties.map((property) => [property, { enum: POSITION_SETTINGS }]));
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  notStaticClassProp: '\'{{name}}\' should be declared as a static class property.',
+  notGetterClassFunc: '\'{{name}}\' should be declared as a static getter class function.',
+  declareOutsideClass: '\'{{name}}\' should be declared outside the class body.',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforces where React component static properties should be positioned.',
+      category: 'Stylistic Issues',
+      recommended: false,
+      url: docsUrl('static-property-placement'),
+    },
+    fixable: null, // or 'code' or 'whitespace'
+
+    messages,
+
+    schema: [
+      { enum: POSITION_SETTINGS },
+      {
+        type: 'object',
+        properties: schemaProperties,
+        additionalProperties: false,
+      },
+    ],
+  },
+
+  create: Components.detect((context, components, utils) => {
+    // variables should be defined here
+    const options = context.options;
+    const defaultCheckType = options[0] || STATIC_PUBLIC_FIELD;
+    const hasAdditionalConfig = options.length > 1;
+    const additionalConfig = hasAdditionalConfig ? options[1] : {};
+
+    // Set config
+    const config = fromEntries(classProperties.map((property) => [
+      property,
+      additionalConfig[property] || defaultCheckType,
+    ]));
+
+    // ----------------------------------------------------------------------
+    // Helpers
+    // ----------------------------------------------------------------------
+
+    /**
+      * Checks if we are declaring context in class
+      * @param {ASTNode} node
+      * @returns {boolean} True if we are declaring context in class, false if not.
+     */
+    function isContextInClass(node) {
+      let blockNode;
+      let scope = getScope(context, node);
+      while (scope) {
+        blockNode = scope.block;
+        if (blockNode && blockNode.type === 'ClassDeclaration') {
+          return true;
+        }
+        scope = scope.upper;
+      }
+
+      return false;
+    }
+
+    /**
+     * Check if we should report this property node
+     * @param {ASTNode} node
+     * @param {string} expectedRule
+     */
+    function reportNodeIncorrectlyPositioned(node, expectedRule) {
+      // Detect if this node is an expected property declaration adn return the property name
+      const name = classProperties.find((propertyName) => {
+        if (propertiesToCheck[propertyName](node)) {
+          return !!propertyName;
+        }
+
+        return false;
+      });
+
+      // If name is set but the configured rule does not match expected then report error
+      if (
+        name
+        && (
+          config[name] !== expectedRule
+          || (!node.static && (config[name] === STATIC_PUBLIC_FIELD || config[name] === STATIC_GETTER))
+        )
+      ) {
+        const messageId = ERROR_MESSAGES[config[name]];
+        report(context, messages[messageId], messageId, {
+          node,
+          data: { name },
+        });
+      }
+    }
+
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+    return {
+      'ClassProperty, PropertyDefinition'(node) {
+        if (!componentUtil.getParentES6Component(context, node)) {
+          return;
+        }
+
+        reportNodeIncorrectlyPositioned(node, STATIC_PUBLIC_FIELD);
+      },
+
+      MemberExpression(node) {
+        // If definition type is undefined then it must not be a defining expression or if the definition is inside a
+        // class body then skip this node.
+        const right = node.parent.right;
+        if (!right || right.type === 'undefined' || isContextInClass(node)) {
+          return;
+        }
+
+        // Get the related component
+        const relatedComponent = utils.getRelatedComponent(node);
+
+        // If the related component is not an ES6 component then skip this node
+        if (!relatedComponent || !componentUtil.isES6Component(relatedComponent.node, context)) {
+          return;
+        }
+
+        // Report if needed
+        reportNodeIncorrectlyPositioned(node, PROPERTY_ASSIGNMENT);
+      },
+
+      MethodDefinition(node) {
+        // If the function is inside a class and is static getter then check if correctly positioned
+        if (
+          componentUtil.getParentES6Component(context, node)
+          && node.static
+          && node.kind === 'get'
+        ) {
+          // Report error if needed
+          reportNodeIncorrectlyPositioned(node, STATIC_GETTER);
+        }
+      },
+    };
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=style-prop-object.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"style-prop-object.d.ts","sourceRoot":"","sources":["style-prop-object.js"],"names":[],"mappings":"wBAoBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/style-prop-object.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,145 @@
+/**
+ * @fileoverview Enforce style prop value is an object
+ * @author David Petersen
+ */
+
+'use strict';
+
+const variableUtil = require('../util/variable');
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const messages = {
+  stylePropNotObject: 'Style prop value must be an object',
+};
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Enforce style prop value is an object',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('style-prop-object'),
+    },
+
+    messages,
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allow: {
+            type: 'array',
+            items: {
+              type: 'string',
+            },
+            additionalItems: false,
+            uniqueItems: true,
+          },
+        },
+      },
+    ],
+  },
+
+  create(context) {
+    const allowed = new Set(((context.options.length > 0) && context.options[0].allow) || []);
+
+    /**
+     * @param {ASTNode} expression An Identifier node
+     * @returns {boolean}
+     */
+    function isNonNullaryLiteral(expression) {
+      return expression.type === 'Literal' && expression.value !== null;
+    }
+
+    /**
+     * @param {object} node A Identifier node
+     */
+    function checkIdentifiers(node) {
+      const variable = variableUtil.getVariableFromContext(context, node, node.name);
+
+      if (!variable || !variable.defs[0] || !variable.defs[0].node.init) {
+        return;
+      }
+
+      if (isNonNullaryLiteral(variable.defs[0].node.init)) {
+        report(context, messages.stylePropNotObject, 'stylePropNotObject', {
+          node,
+        });
+      }
+    }
+
+    return {
+      CallExpression(node) {
+        if (
+          isCreateElement(context, node)
+          && node.arguments.length > 1
+        ) {
+          if ('name' in node.arguments[0] && node.arguments[0].name) {
+            // store name of component
+            const componentName = node.arguments[0].name;
+
+            // allowed list contains the name
+            if (allowed.has(componentName)) {
+              // abort operation
+              return;
+            }
+          }
+          if (node.arguments[1].type === 'ObjectExpression') {
+            const style = node.arguments[1].properties.find((property) => (
+              'key' in property
+              && property.key
+              && 'name' in property.key
+              && property.key.name === 'style'
+              && !property.computed
+            ));
+
+            if (style && 'value' in style) {
+              if (style.value.type === 'Identifier') {
+                checkIdentifiers(style.value);
+              } else if (isNonNullaryLiteral(style.value)) {
+                report(context, messages.stylePropNotObject, 'stylePropNotObject', {
+                  node: style.value,
+                });
+              }
+            }
+          }
+        }
+      },
+
+      JSXAttribute(node) {
+        if (!node.value || node.name.name !== 'style') {
+          return;
+        }
+        // store parent element
+        const parentElement = node.parent;
+
+        // parent element is a JSXOpeningElement
+        if (parentElement && parentElement.type === 'JSXOpeningElement') {
+          // get the name of the JSX element
+          const name = parentElement.name && parentElement.name.name;
+
+          // allowed list contains the name
+          if (allowed.has(name)) {
+            // abort operation
+            return;
+          }
+        }
+
+        if (node.value.type !== 'JSXExpressionContainer' || isNonNullaryLiteral(node.value.expression)) {
+          report(context, messages.stylePropNotObject, 'stylePropNotObject', {
+            node,
+          });
+        } else if (node.value.expression.type === 'Identifier') {
+          checkIdentifiers(node.value.expression);
+        }
+      },
+    };
+  },
+};
Index: frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare const _exports: import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=void-dom-elements-no-children.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"void-dom-elements-no-children.d.ts","sourceRoot":"","sources":["void-dom-elements-no-children.js"],"names":[],"mappings":"wBAiDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
Index: frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/rules/void-dom-elements-no-children.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,166 @@
+/**
+ * @fileoverview Prevent void elements (e.g. <img />, <br />) from receiving
+ *   children
+ * @author Joe Lencioni
+ */
+
+'use strict';
+
+const has = require('hasown');
+
+const docsUrl = require('../util/docsUrl');
+const isCreateElement = require('../util/isCreateElement');
+const report = require('../util/report');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+// Using an object here to avoid array scan. We should switch to Set once
+// support is good enough.
+const VOID_DOM_ELEMENTS = {
+  area: true,
+  base: true,
+  br: true,
+  col: true,
+  embed: true,
+  hr: true,
+  img: true,
+  input: true,
+  keygen: true,
+  link: true,
+  menuitem: true,
+  meta: true,
+  param: true,
+  source: true,
+  track: true,
+  wbr: true,
+};
+
+function isVoidDOMElement(elementName) {
+  return has(VOID_DOM_ELEMENTS, elementName);
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+const noChildrenInVoidEl = 'Void DOM element <{{element}} /> cannot receive children.';
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow void DOM elements (e.g. `<img />`, `<br />`) from receiving children',
+      category: 'Best Practices',
+      recommended: false,
+      url: docsUrl('void-dom-elements-no-children'),
+    },
+
+    messages: {
+      noChildrenInVoidEl,
+    },
+
+    schema: [],
+  },
+
+  create: (context) => ({
+    JSXElement(node) {
+      const elementName = node.openingElement.name.name;
+
+      if (!isVoidDOMElement(elementName)) {
+        // e.g. <div />
+        return;
+      }
+
+      if (node.children.length > 0) {
+        // e.g. <br>Foo</br>
+        report(context, noChildrenInVoidEl, 'noChildrenInVoidEl', {
+          node,
+          data: {
+            element: elementName,
+          },
+        });
+      }
+
+      const attributes = node.openingElement.attributes;
+
+      const hasChildrenAttributeOrDanger = attributes.some((attribute) => {
+        if (!attribute.name) {
+          return false;
+        }
+
+        return attribute.name.name === 'children' || attribute.name.name === 'dangerouslySetInnerHTML';
+      });
+
+      if (hasChildrenAttributeOrDanger) {
+        // e.g. <br children="Foo" />
+        report(context, noChildrenInVoidEl, 'noChildrenInVoidEl', {
+          node,
+          data: {
+            element: elementName,
+          },
+        });
+      }
+    },
+
+    CallExpression(node) {
+      if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') {
+        return;
+      }
+
+      if (!isCreateElement(context, node)) {
+        return;
+      }
+
+      const args = node.arguments;
+
+      if (args.length < 1) {
+        // React.createElement() should not crash linter
+        return;
+      }
+
+      const elementName = 'value' in args[0] ? args[0].value : undefined;
+
+      if (!isVoidDOMElement(elementName)) {
+        // e.g. React.createElement('div');
+        return;
+      }
+
+      if (args.length < 2 || args[1].type !== 'ObjectExpression') {
+        return;
+      }
+
+      const firstChild = args[2];
+      if (firstChild) {
+        // e.g. React.createElement('br', undefined, 'Foo')
+        report(context, noChildrenInVoidEl, 'noChildrenInVoidEl', {
+          node,
+          data: {
+            element: elementName,
+          },
+        });
+      }
+
+      const props = args[1].properties;
+
+      const hasChildrenPropOrDanger = props.some((prop) => {
+        if (!('key' in prop) || !prop.key || !('name' in prop.key)) {
+          return false;
+        }
+
+        return prop.key.name === 'children' || prop.key.name === 'dangerouslySetInnerHTML';
+      });
+
+      if (hasChildrenPropOrDanger) {
+        // e.g. React.createElement('br', { children: 'Foo' })
+        report(context, noChildrenInVoidEl, 'noChildrenInVoidEl', {
+          node,
+          data: {
+            element: elementName,
+          },
+        });
+      }
+    },
+  }),
+};
Index: frontend/node_modules/eslint-plugin-react/lib/types.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,29 @@
+import eslint from 'eslint';
+import estree from 'estree';
+
+declare global {
+  interface ASTNode extends estree.BaseNode {
+    [_: string]: any; // TODO: fixme
+  }
+  type Scope = eslint.Scope.Scope;
+  type Token = eslint.AST.Token;
+  type Fixer = eslint.Rule.RuleFixer;
+  type JSXAttribute = ASTNode;
+  type JSXElement = ASTNode;
+  type JSXFragment = ASTNode;
+  type JSXOpeningElement = ASTNode;
+  type JSXSpreadAttribute = ASTNode;
+
+  type Context = eslint.Rule.RuleContext;
+
+  type TypeDeclarationBuilder = (annotation: ASTNode, parentName: string, seen: Set<typeof annotation>) => object;
+
+  type TypeDeclarationBuilders = {
+    [k in string]: TypeDeclarationBuilder;
+  };
+
+  type UnionTypeDefinition = {
+    type: 'union' | 'shape';
+    children: unknown[];
+  };
+}
Index: frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+declare const _exports: typeof Components & {
+    detect(rule: any): (context?: any) => {
+        [_: string]: Function;
+    };
+};
+export = _exports;
+/**
+ * Components
+ */
+declare class Components {
+    /**
+     * Add a node to the components list, or update it if it's already in the list
+     *
+     * @param {ASTNode} node The AST node being added.
+     * @param {number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
+     * @returns {Object} Added component object
+     */
+    add(node: ASTNode, confidence: number): any;
+    /**
+     * Find a component in the list using its node
+     *
+     * @param {ASTNode} node The AST node being searched.
+     * @returns {Object} Component object, undefined if the component is not found or has confidence value of 0.
+     */
+    get(node: ASTNode): any;
+    /**
+     * Update a component in the list
+     *
+     * @param {ASTNode} node The AST node being updated.
+     * @param {Object} props Additional properties to add to the component.
+     */
+    set(node: ASTNode, props: any): void;
+    /**
+     * Return the components list
+     * Components for which we are not confident are not returned
+     *
+     * @returns {Object} Components list
+     */
+    list(): any;
+    /**
+     * Return the length of the components list
+     * Components for which we are not confident are not counted
+     *
+     * @returns {number} Components list length
+     */
+    length(): number;
+    /**
+     * Return the node naming the default React import
+     * It can be used to determine the local name of import, even if it's imported
+     * with an unusual name.
+     *
+     * @returns {ASTNode} React default import node
+     */
+    getDefaultReactImports(): ASTNode;
+    /**
+     * Return the nodes of all React named imports
+     *
+     * @returns {Object} The list of React named imports
+     */
+    getNamedReactImports(): any;
+    /**
+     * Add the default React import specifier to the scope
+     *
+     * @param {ASTNode} specifier The AST Node of the default React import
+     * @returns {void}
+     */
+    addDefaultReactImport(specifier: ASTNode): void;
+    /**
+     * Add a named React import specifier to the scope
+     *
+     * @param {ASTNode} specifier The AST Node of a named React import
+     * @returns {void}
+     */
+    addNamedReactImport(specifier: ASTNode): void;
+}
+//# sourceMappingURL=Components.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/Components.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"Components.d.ts","sourceRoot":"","sources":["Components.js"],"names":[],"mappings":";;;;;;AA2DA;;GAEG;AACH;IAME;;;;;;OAMG;IACH,UAJW,OAAO,cACP,MAAM,OAmBhB;IAED;;;;;OAKG;IACH,UAHW,OAAO,OAUjB;IAED;;;;;OAKG;IACH,UAHW,OAAO,oBAwBjB;IAED;;;;;OAKG;IACH,YAoCC;IAED;;;;;OAKG;IACH,UAFa,MAAM,CAKlB;IAED;;;;;;OAMG;IACH,0BAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,4BAEC;IAED;;;;;OAKG;IACH,iCAHW,OAAO,GACL,IAAI,CAOhB;IAED;;;;;OAKG;IACH,+BAHW,OAAO,GACL,IAAI,CAOhB;CACF"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/Components.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/Components.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/Components.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,959 @@
+/**
+ * @fileoverview Utility class and functions for React components detection
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const arrayIncludes = require('array-includes');
+const fromEntries = require('object.fromentries');
+const values = require('object.values');
+const iterFrom = require('es-iterator-helpers/Iterator.from');
+const map = require('es-iterator-helpers/Iterator.prototype.map');
+
+const variableUtil = require('./variable');
+const pragmaUtil = require('./pragma');
+const astUtil = require('./ast');
+const componentUtil = require('./componentUtil');
+const propTypesUtil = require('./propTypes');
+const jsxUtil = require('./jsx');
+const usedPropTypesUtil = require('./usedPropTypes');
+const defaultPropsUtil = require('./defaultProps');
+const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
+const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');
+const eslintUtil = require('./eslint');
+
+const getScope = eslintUtil.getScope;
+const getText = eslintUtil.getText;
+
+function getId(node) {
+  return node ? `${node.range[0]}:${node.range[1]}` : '';
+}
+
+function usedPropTypesAreEquivalent(propA, propB) {
+  if (propA.name === propB.name) {
+    if (!propA.allNames && !propB.allNames) {
+      return true;
+    }
+    if (Array.isArray(propA.allNames) && Array.isArray(propB.allNames) && propA.allNames.join('') === propB.allNames.join('')) {
+      return true;
+    }
+    return false;
+  }
+  return false;
+}
+
+function mergeUsedPropTypes(propsList, newPropsList) {
+  const propsToAdd = newPropsList.filter((newProp) => {
+    const newPropIsAlreadyInTheList = propsList.some((prop) => usedPropTypesAreEquivalent(prop, newProp));
+    return !newPropIsAlreadyInTheList;
+  });
+
+  return propsList.concat(propsToAdd);
+}
+
+const USE_HOOK_PREFIX_REGEX = /^use[A-Z]/;
+
+const Lists = new WeakMap();
+const ReactImports = new WeakMap();
+
+/**
+ * Components
+ */
+class Components {
+  constructor() {
+    Lists.set(this, {});
+    ReactImports.set(this, {});
+  }
+
+  /**
+   * Add a node to the components list, or update it if it's already in the list
+   *
+   * @param {ASTNode} node The AST node being added.
+   * @param {number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
+   * @returns {Object} Added component object
+   */
+  add(node, confidence) {
+    const id = getId(node);
+    const list = Lists.get(this);
+    if (list[id]) {
+      if (confidence === 0 || list[id].confidence === 0) {
+        list[id].confidence = 0;
+      } else {
+        list[id].confidence = Math.max(list[id].confidence, confidence);
+      }
+      return list[id];
+    }
+    list[id] = {
+      node,
+      confidence,
+    };
+    return list[id];
+  }
+
+  /**
+   * Find a component in the list using its node
+   *
+   * @param {ASTNode} node The AST node being searched.
+   * @returns {Object} Component object, undefined if the component is not found or has confidence value of 0.
+   */
+  get(node) {
+    const id = getId(node);
+    const item = Lists.get(this)[id];
+    if (item && item.confidence >= 1) {
+      return item;
+    }
+    return null;
+  }
+
+  /**
+   * Update a component in the list
+   *
+   * @param {ASTNode} node The AST node being updated.
+   * @param {Object} props Additional properties to add to the component.
+   */
+  set(node, props) {
+    const list = Lists.get(this);
+    let component = list[getId(node)];
+    while (!component || component.confidence < 1) {
+      node = node.parent;
+      if (!node) {
+        return;
+      }
+      component = list[getId(node)];
+    }
+
+    Object.assign(
+      component,
+      props,
+      {
+        usedPropTypes: mergeUsedPropTypes(
+          component.usedPropTypes || [],
+          props.usedPropTypes || []
+        ),
+      }
+    );
+  }
+
+  /**
+   * Return the components list
+   * Components for which we are not confident are not returned
+   *
+   * @returns {Object} Components list
+   */
+  list() {
+    const thisList = Lists.get(this);
+    const list = {};
+    const usedPropTypes = {};
+
+    // Find props used in components for which we are not confident
+    Object.keys(thisList).filter((i) => thisList[i].confidence < 2).forEach((i) => {
+      let component = null;
+      let node = null;
+      node = thisList[i].node;
+      while (!component && node.parent) {
+        node = node.parent;
+        // Stop moving up if we reach a decorator
+        if (node.type === 'Decorator') {
+          break;
+        }
+        component = this.get(node);
+      }
+      if (component) {
+        const newUsedProps = (thisList[i].usedPropTypes || []).filter((propType) => !propType.node || propType.node.kind !== 'init');
+
+        const componentId = getId(component.node);
+
+        usedPropTypes[componentId] = mergeUsedPropTypes(usedPropTypes[componentId] || [], newUsedProps);
+      }
+    });
+
+    // Assign used props in not confident components to the parent component
+    Object.keys(thisList).filter((j) => thisList[j].confidence >= 2).forEach((j) => {
+      const id = getId(thisList[j].node);
+      list[j] = thisList[j];
+      if (usedPropTypes[id]) {
+        list[j].usedPropTypes = mergeUsedPropTypes(list[j].usedPropTypes || [], usedPropTypes[id]);
+      }
+    });
+    return list;
+  }
+
+  /**
+   * Return the length of the components list
+   * Components for which we are not confident are not counted
+   *
+   * @returns {number} Components list length
+   */
+  length() {
+    const list = Lists.get(this);
+    return values(list).filter((component) => component.confidence >= 2).length;
+  }
+
+  /**
+   * Return the node naming the default React import
+   * It can be used to determine the local name of import, even if it's imported
+   * with an unusual name.
+   *
+   * @returns {ASTNode} React default import node
+   */
+  getDefaultReactImports() {
+    return ReactImports.get(this).defaultReactImports;
+  }
+
+  /**
+   * Return the nodes of all React named imports
+   *
+   * @returns {Object} The list of React named imports
+   */
+  getNamedReactImports() {
+    return ReactImports.get(this).namedReactImports;
+  }
+
+  /**
+   * Add the default React import specifier to the scope
+   *
+   * @param {ASTNode} specifier The AST Node of the default React import
+   * @returns {void}
+   */
+  addDefaultReactImport(specifier) {
+    const info = ReactImports.get(this);
+    ReactImports.set(this, Object.assign({}, info, {
+      defaultReactImports: (info.defaultReactImports || []).concat(specifier),
+    }));
+  }
+
+  /**
+   * Add a named React import specifier to the scope
+   *
+   * @param {ASTNode} specifier The AST Node of a named React import
+   * @returns {void}
+   */
+  addNamedReactImport(specifier) {
+    const info = ReactImports.get(this);
+    ReactImports.set(this, Object.assign({}, info, {
+      namedReactImports: (info.namedReactImports || []).concat(specifier),
+    }));
+  }
+}
+
+function getWrapperFunctions(context, pragma) {
+  const componentWrapperFunctions = context.settings.componentWrapperFunctions || [];
+
+  // eslint-disable-next-line arrow-body-style
+  return componentWrapperFunctions.map((wrapperFunction) => {
+    return typeof wrapperFunction === 'string'
+      ? { property: wrapperFunction }
+      : Object.assign({}, wrapperFunction, {
+        object: wrapperFunction.object === '<pragma>' ? pragma : wrapperFunction.object,
+      });
+  }).concat([
+    { property: 'forwardRef', object: pragma },
+    { property: 'memo', object: pragma },
+  ]);
+}
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * Merge many eslint rules into one
+ * @param {{[_: string]: Function}[]} rules the returned values for eslint rule.create(context)
+ * @returns {{[_: string]: Function}} merged rule
+ */
+function mergeRules(rules) {
+  /** @type {Map<string, Function[]>} */
+  const handlersByKey = new Map();
+  rules.forEach((rule) => {
+    Object.keys(rule).forEach((key) => {
+      const fns = handlersByKey.get(key);
+      if (!fns) {
+        handlersByKey.set(key, [rule[key]]);
+      } else {
+        fns.push(rule[key]);
+      }
+    });
+  });
+
+  /** @type {{ [key: string]: Function }} */
+  return fromEntries(map(iterFrom(handlersByKey), (entry) => [
+    entry[0],
+    function mergedHandler(node) {
+      entry[1].forEach((fn) => {
+        fn(node);
+      });
+    },
+  ]));
+}
+
+function componentRule(rule, context) {
+  const pragma = pragmaUtil.getFromContext(context);
+  const components = new Components();
+  const wrapperFunctions = getWrapperFunctions(context, pragma);
+
+  // Utilities for component detection
+  const utils = {
+    /**
+     * Check if variable is destructured from pragma import
+     *
+     * @param {ASTNode} node The AST node to check
+     * @param {string} variable The variable name to check
+     * @returns {boolean} True if createElement is destructured from the pragma
+     */
+    isDestructuredFromPragmaImport(node, variable) {
+      return isDestructuredFromPragmaImport(context, node, variable);
+    },
+
+    /**
+     * @param {ASTNode} node
+     * @param {boolean=} strict
+     * @returns {boolean}
+     */
+    isReturningJSX(node, strict) {
+      return jsxUtil.isReturningJSX(context, node, strict, true);
+    },
+
+    isReturningJSXOrNull(node, strict) {
+      return jsxUtil.isReturningJSX(context, node, strict);
+    },
+
+    isReturningOnlyNull(node) {
+      return jsxUtil.isReturningOnlyNull(node, context);
+    },
+
+    getPragmaComponentWrapper(node) {
+      let isPragmaComponentWrapper;
+      let currentNode = node;
+      let prevNode;
+      do {
+        currentNode = currentNode.parent;
+        isPragmaComponentWrapper = this.isPragmaComponentWrapper(currentNode);
+        if (isPragmaComponentWrapper) {
+          prevNode = currentNode;
+        }
+      } while (isPragmaComponentWrapper);
+
+      return prevNode;
+    },
+
+    getComponentNameFromJSXElement(node) {
+      if (node.type !== 'JSXElement') {
+        return null;
+      }
+      if (node.openingElement && node.openingElement.name && node.openingElement.name.name) {
+        return node.openingElement.name.name;
+      }
+      return null;
+    },
+
+    /**
+     * Getting the first JSX element's name.
+     * @param {object} node
+     * @returns {string | null}
+     */
+    getNameOfWrappedComponent(node) {
+      if (node.length < 1) {
+        return null;
+      }
+      const body = node[0].body;
+      if (!body) {
+        return null;
+      }
+      if (body.type === 'JSXElement') {
+        return this.getComponentNameFromJSXElement(body);
+      }
+      if (body.type === 'BlockStatement') {
+        const jsxElement = body.body.find((item) => item.type === 'ReturnStatement');
+        return jsxElement
+          && jsxElement.argument
+          && this.getComponentNameFromJSXElement(jsxElement.argument);
+      }
+      return null;
+    },
+
+    /**
+     * Get the list of names of components created till now
+     * @returns {string | boolean}
+     */
+    getDetectedComponents() {
+      const list = components.list();
+      return values(list).filter((val) => {
+        if (val.node.type === 'ClassDeclaration') {
+          return true;
+        }
+        if (
+          val.node.type === 'ArrowFunctionExpression'
+          && val.node.parent
+          && val.node.parent.type === 'VariableDeclarator'
+          && val.node.parent.id
+        ) {
+          return true;
+        }
+        return false;
+      }).map((val) => {
+        if (val.node.type === 'ArrowFunctionExpression') return val.node.parent.id.name;
+        return val.node.id && val.node.id.name;
+      });
+    },
+
+    /**
+     * It will check whether memo/forwardRef is wrapping existing component or
+     * creating a new one.
+     * @param {object} node
+     * @returns {boolean}
+     */
+    nodeWrapsComponent(node) {
+      const childComponent = this.getNameOfWrappedComponent(node.arguments);
+      const componentList = this.getDetectedComponents();
+      return !!childComponent && arrayIncludes(componentList, childComponent);
+    },
+
+    isPragmaComponentWrapper(node) {
+      if (!astUtil.isCallExpression(node)) {
+        return false;
+      }
+
+      return wrapperFunctions.some((wrapperFunction) => {
+        if (node.callee.type === 'MemberExpression') {
+          return wrapperFunction.object
+            && wrapperFunction.object === node.callee.object.name
+            && wrapperFunction.property === node.callee.property.name
+            && !this.nodeWrapsComponent(node);
+        }
+        return wrapperFunction.property === node.callee.name
+          && (!wrapperFunction.object
+            // Functions coming from the current pragma need special handling
+            || (wrapperFunction.object === pragma && this.isDestructuredFromPragmaImport(node, node.callee.name))
+          );
+      });
+    },
+
+    /**
+     * Find a return statement in the current node
+     *
+     * @param {ASTNode} node The AST node being checked
+     */
+    findReturnStatement: astUtil.findReturnStatement,
+
+    /**
+     * Get the parent component node from the current scope
+     * @param {ASTNode} node
+     *
+     * @returns {ASTNode} component node, null if we are not in a component
+     */
+    getParentComponent(node) {
+      return (
+        componentUtil.getParentES6Component(context, node)
+        || componentUtil.getParentES5Component(context, node)
+        || utils.getParentStatelessComponent(node)
+      );
+    },
+
+    /**
+     * @param {ASTNode} node
+     * @returns {boolean}
+     */
+    isInAllowedPositionForComponent(node) {
+      switch (node.parent.type) {
+        case 'VariableDeclarator':
+        case 'AssignmentExpression':
+        case 'Property':
+        case 'ReturnStatement':
+        case 'ExportDefaultDeclaration':
+        case 'ArrowFunctionExpression': {
+          return true;
+        }
+        case 'SequenceExpression': {
+          return utils.isInAllowedPositionForComponent(node.parent)
+            && node === node.parent.expressions[node.parent.expressions.length - 1];
+        }
+        default:
+          return false;
+      }
+    },
+
+    /**
+     * Get node if node is a stateless component, or node.parent in cases like
+     * `React.memo` or `React.forwardRef`. Otherwise returns `undefined`.
+     * @param {ASTNode} node
+     * @returns {ASTNode | undefined}
+     */
+    getStatelessComponent(node) {
+      const parent = node.parent;
+      if (
+        node.type === 'FunctionDeclaration'
+        && (!node.id || isFirstLetterCapitalized(node.id.name))
+        && utils.isReturningJSXOrNull(node)
+      ) {
+        return node;
+      }
+
+      if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
+        const isPropertyAssignment = parent.type === 'AssignmentExpression'
+          && parent.left.type === 'MemberExpression';
+        const isModuleExportsAssignment = isPropertyAssignment
+          && parent.left.object.name === 'module'
+          && parent.left.property.name === 'exports';
+
+        if (node.parent.type === 'ExportDefaultDeclaration') {
+          if (utils.isReturningJSX(node)) {
+            return node;
+          }
+          return undefined;
+        }
+
+        if (node.parent.type === 'VariableDeclarator' && utils.isReturningJSXOrNull(node)) {
+          if (isFirstLetterCapitalized(node.parent.id.name)) {
+            return node;
+          }
+          return undefined;
+        }
+
+        // case: const any = () => { return (props) => null }
+        // case: const any = () => (props) => null
+        if (
+          (node.parent.type === 'ReturnStatement' || (node.parent.type === 'ArrowFunctionExpression' && node.parent.expression))
+          && !utils.isReturningJSX(node)
+        ) {
+          return undefined;
+        }
+
+        // case: any = () => { return => null }
+        // case: any = () => null
+        if (node.parent.type === 'AssignmentExpression' && !isPropertyAssignment && utils.isReturningJSXOrNull(node)) {
+          if (isFirstLetterCapitalized(node.parent.left.name)) {
+            return node;
+          }
+          return undefined;
+        }
+
+        // case: any = () => () => null
+        if (node.parent.type === 'ArrowFunctionExpression' && node.parent.parent.type === 'AssignmentExpression' && !isPropertyAssignment && utils.isReturningJSXOrNull(node)) {
+          if (isFirstLetterCapitalized(node.parent.parent.left.name)) {
+            return node;
+          }
+          return undefined;
+        }
+
+        // case: { any: () => () => null }
+        if (node.parent.type === 'ArrowFunctionExpression' && node.parent.parent.type === 'Property' && !isPropertyAssignment && utils.isReturningJSXOrNull(node)) {
+          if (isFirstLetterCapitalized(node.parent.parent.key.name)) {
+            return node;
+          }
+          return undefined;
+        }
+
+        // case: any = function() {return function() {return null;};}
+        if (node.parent.type === 'ReturnStatement') {
+          if (isFirstLetterCapitalized(node.id && node.id.name)) {
+            return node;
+          }
+          const functionExpr = node.parent.parent.parent;
+          if (functionExpr.parent.type === 'AssignmentExpression' && !isPropertyAssignment && utils.isReturningJSXOrNull(node)) {
+            if (isFirstLetterCapitalized(functionExpr.parent.left.name)) {
+              return node;
+            }
+            return undefined;
+          }
+        }
+
+        // case: { any: function() {return function() {return null;};} }
+        if (node.parent.type === 'ReturnStatement') {
+          const functionExpr = node.parent.parent.parent;
+          if (functionExpr.parent.type === 'Property' && !isPropertyAssignment && utils.isReturningJSXOrNull(node)) {
+            if (isFirstLetterCapitalized(functionExpr.parent.key.name)) {
+              return node;
+            }
+            return undefined;
+          }
+        }
+
+        // for case abc = { [someobject.somekey]: props => { ... return not-jsx } }
+        if (
+          node.parent
+          && node.parent.key
+          && node.parent.key.type === 'MemberExpression'
+          && !utils.isReturningJSX(node)
+          && !utils.isReturningOnlyNull(node)
+        ) {
+          return undefined;
+        }
+
+        if (
+          node.parent.type === 'Property' && (
+            (node.parent.method && !node.parent.computed) // case: { f() { return ... } }
+            || (!node.id && !node.parent.computed) // case: { f: () => ... }
+          )
+        ) {
+          if (
+            isFirstLetterCapitalized(node.parent.key.name)
+            && utils.isReturningJSX(node)
+          ) {
+            return node;
+          }
+          return undefined;
+        }
+
+        // Case like `React.memo(() => <></>)` or `React.forwardRef(...)`
+        const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node);
+        if (pragmaComponentWrapper && utils.isReturningJSXOrNull(node)) {
+          return pragmaComponentWrapper;
+        }
+
+        if (!(utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node))) {
+          return undefined;
+        }
+
+        if (utils.isParentComponentNotStatelessComponent(node)) {
+          return undefined;
+        }
+
+        if (node.id) {
+          return isFirstLetterCapitalized(node.id.name) ? node : undefined;
+        }
+
+        if (
+          isPropertyAssignment
+          && !isModuleExportsAssignment
+          && !isFirstLetterCapitalized(parent.left.property.name)
+        ) {
+          return undefined;
+        }
+
+        if (parent.type === 'Property' && utils.isReturningOnlyNull(node)) {
+          return undefined;
+        }
+
+        return node;
+      }
+
+      return undefined;
+    },
+
+    /**
+     * Get the parent stateless component node from the current scope
+     *
+     * @param {ASTNode} node The AST node being checked
+     * @returns {ASTNode} component node, null if we are not in a component
+     */
+    getParentStatelessComponent(node) {
+      let scope = getScope(context, node);
+      while (scope) {
+        const statelessComponent = utils.getStatelessComponent(scope.block);
+        if (statelessComponent) {
+          return statelessComponent;
+        }
+        scope = scope.upper;
+      }
+      return null;
+    },
+
+    /**
+     * Get the related component from a node
+     *
+     * @param {ASTNode} node The AST node being checked (must be a MemberExpression).
+     * @returns {ASTNode | null} component node, null if we cannot find the component
+     */
+    getRelatedComponent(node) {
+      let i;
+      let j;
+      let k;
+      let l;
+      let componentNode;
+      // Get the component path
+      const componentPath = [];
+      let nodeTemp = node;
+      while (nodeTemp) {
+        if (nodeTemp.property && nodeTemp.property.type === 'Identifier') {
+          componentPath.push(nodeTemp.property.name);
+        }
+        if (nodeTemp.object && nodeTemp.object.type === 'Identifier') {
+          componentPath.push(nodeTemp.object.name);
+        }
+        nodeTemp = nodeTemp.object;
+      }
+      componentPath.reverse();
+      const componentName = componentPath.slice(0, componentPath.length - 1).join('.');
+
+      // Find the variable in the current scope
+      const variableName = componentPath.shift();
+      if (!variableName) {
+        return null;
+      }
+      const variableInScope = variableUtil.getVariableFromContext(context, node, variableName);
+      if (!variableInScope) {
+        return null;
+      }
+
+      // Try to find the component using variable references
+      variableInScope.references.some((ref) => {
+        let refId = ref.identifier;
+        if (refId.parent && refId.parent.type === 'MemberExpression') {
+          refId = refId.parent;
+        }
+        if (getText(context, refId) !== componentName) {
+          return false;
+        }
+        if (refId.type === 'MemberExpression') {
+          componentNode = refId.parent.right;
+        } else if (
+          refId.parent
+          && refId.parent.type === 'VariableDeclarator'
+          && refId.parent.init
+          && refId.parent.init.type !== 'Identifier'
+        ) {
+          componentNode = refId.parent.init;
+        }
+        return true;
+      });
+
+      if (componentNode) {
+        // Return the component
+        return components.add(componentNode, 1);
+      }
+
+      // Try to find the component using variable declarations
+      const defs = variableInScope.defs;
+      const defInScope = defs.find((def) => (
+        def.type === 'ClassName'
+        || def.type === 'FunctionName'
+        || def.type === 'Variable'
+      ));
+      if (!defInScope || !defInScope.node) {
+        return null;
+      }
+      componentNode = defInScope.node.init || defInScope.node;
+
+      // Traverse the node properties to the component declaration
+      for (i = 0, j = componentPath.length; i < j; i++) {
+        if (!componentNode.properties) {
+          continue; // eslint-disable-line no-continue
+        }
+        for (k = 0, l = componentNode.properties.length; k < l; k++) {
+          if (componentNode.properties[k].key && componentNode.properties[k].key.name === componentPath[i]) {
+            componentNode = componentNode.properties[k];
+            break;
+          }
+        }
+        if (!componentNode || !componentNode.value) {
+          return null;
+        }
+        componentNode = componentNode.value;
+      }
+
+      // Return the component
+      return components.add(componentNode, 1);
+    },
+
+    isParentComponentNotStatelessComponent(node) {
+      return !!(
+        node.parent
+        && node.parent.key
+        && node.parent.key.type === 'Identifier'
+        // custom component functions must start with a capital letter (returns false otherwise)
+        && node.parent.key.name.charAt(0) === node.parent.key.name.charAt(0).toLowerCase()
+        // react render function cannot have params
+        && !!(node.params || []).length
+      );
+    },
+
+    /**
+     * Identify whether a node (CallExpression) is a call to a React hook
+     *
+     * @param {ASTNode} node The AST node being searched. (expects CallExpression)
+     * @param {('useCallback'|'useContext'|'useDebugValue'|'useEffect'|'useImperativeHandle'|'useLayoutEffect'|'useMemo'|'useReducer'|'useRef'|'useState')[]} [expectedHookNames] React hook names to which search is limited.
+     * @returns {boolean} True if the node is a call to a React hook
+     */
+    isReactHookCall(node, expectedHookNames) {
+      if (!astUtil.isCallExpression(node)) {
+        return false;
+      }
+
+      const defaultReactImports = components.getDefaultReactImports();
+      const namedReactImports = components.getNamedReactImports();
+
+      const defaultReactImportName = defaultReactImports
+        && defaultReactImports[0]
+        && defaultReactImports[0].local.name;
+      const reactHookImportSpecifiers = namedReactImports
+        && namedReactImports.filter((specifier) => USE_HOOK_PREFIX_REGEX.test(specifier.imported.name));
+      const reactHookImportNames = reactHookImportSpecifiers
+        && fromEntries(reactHookImportSpecifiers.map((specifier) => [specifier.local.name, specifier.imported.name]));
+
+      const isPotentialReactHookCall = defaultReactImportName
+        && node.callee.type === 'MemberExpression'
+        && node.callee.object.type === 'Identifier'
+        && node.callee.object.name === defaultReactImportName
+        && node.callee.property.type === 'Identifier'
+        && node.callee.property.name.match(USE_HOOK_PREFIX_REGEX);
+
+      const isPotentialHookCall = reactHookImportNames
+        && node.callee.type === 'Identifier'
+        && node.callee.name.match(USE_HOOK_PREFIX_REGEX);
+
+      const scope = (isPotentialReactHookCall || isPotentialHookCall) && getScope(context, node);
+
+      const reactResolvedDefs = isPotentialReactHookCall
+        && scope.references
+        && scope.references.find(
+          (reference) => reference.identifier.name === defaultReactImportName
+        ).resolved.defs;
+
+      const isReactShadowed = isPotentialReactHookCall && reactResolvedDefs
+        && reactResolvedDefs.some((reactDef) => reactDef.type !== 'ImportBinding');
+
+      const potentialHookReference = isPotentialHookCall
+        && scope.references
+        && scope.references.find(
+          (reference) => reactHookImportNames[reference.identifier.name]
+        );
+
+      const hookResolvedDefs = potentialHookReference && potentialHookReference.resolved.defs;
+      const localHookName = (
+        isPotentialReactHookCall
+        && node.callee.property.name
+      ) || (
+        isPotentialHookCall
+        && potentialHookReference
+        && node.callee.name
+      );
+      const isHookShadowed = isPotentialHookCall
+        && hookResolvedDefs
+        && hookResolvedDefs.some(
+          (hookDef) => hookDef.name.name === localHookName
+          && hookDef.type !== 'ImportBinding'
+        );
+
+      const isHookCall = (isPotentialReactHookCall && !isReactShadowed)
+        || (isPotentialHookCall && localHookName && !isHookShadowed);
+
+      if (!isHookCall) {
+        return false;
+      }
+
+      if (!expectedHookNames) {
+        return true;
+      }
+
+      return arrayIncludes(
+        expectedHookNames,
+        (reactHookImportNames && reactHookImportNames[localHookName]) || localHookName
+      );
+    },
+  };
+
+  // Component detection instructions
+  const detectionInstructions = {
+    CallExpression(node) {
+      if (!utils.isPragmaComponentWrapper(node)) {
+        return;
+      }
+      if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
+        components.add(node, 2);
+      }
+    },
+
+    ClassExpression(node) {
+      if (!componentUtil.isES6Component(node, context)) {
+        return;
+      }
+      components.add(node, 2);
+    },
+
+    ClassDeclaration(node) {
+      if (!componentUtil.isES6Component(node, context)) {
+        return;
+      }
+      components.add(node, 2);
+    },
+
+    ObjectExpression(node) {
+      if (!componentUtil.isES5Component(node, context)) {
+        return;
+      }
+      components.add(node, 2);
+    },
+
+    FunctionExpression(node) {
+      if (node.async && node.generator) {
+        components.add(node, 0);
+        return;
+      }
+
+      const component = utils.getStatelessComponent(node);
+      if (!component) {
+        return;
+      }
+      components.add(component, 2);
+    },
+
+    FunctionDeclaration(node) {
+      if (node.async && node.generator) {
+        components.add(node, 0);
+        return;
+      }
+
+      const cNode = utils.getStatelessComponent(node);
+      if (!cNode) {
+        return;
+      }
+      components.add(cNode, 2);
+    },
+
+    ArrowFunctionExpression(node) {
+      const component = utils.getStatelessComponent(node);
+      if (!component) {
+        return;
+      }
+      components.add(component, 2);
+    },
+
+    ThisExpression(node) {
+      const component = utils.getParentStatelessComponent(node);
+      if (!component || !/Function/.test(component.type) || !node.parent.property) {
+        return;
+      }
+      // Ban functions accessing a property on a ThisExpression
+      components.add(node, 0);
+    },
+  };
+
+  // Detect React import specifiers
+  const reactImportInstructions = {
+    ImportDeclaration(node) {
+      const isReactImported = node.source.type === 'Literal' && node.source.value === 'react';
+      if (!isReactImported) {
+        return;
+      }
+
+      node.specifiers.forEach((specifier) => {
+        if (specifier.type === 'ImportDefaultSpecifier') {
+          components.addDefaultReactImport(specifier);
+        }
+        if (specifier.type === 'ImportSpecifier') {
+          components.addNamedReactImport(specifier);
+        }
+      });
+    },
+  };
+
+  const ruleInstructions = rule(context, components, utils);
+  const propTypesInstructions = propTypesUtil(context, components, utils);
+  const usedPropTypesInstructions = usedPropTypesUtil(context, components, utils);
+  const defaultPropsInstructions = defaultPropsUtil(context, components, utils);
+
+  const mergedRule = mergeRules([
+    detectionInstructions,
+    propTypesInstructions,
+    usedPropTypesInstructions,
+    defaultPropsInstructions,
+    reactImportInstructions,
+    ruleInstructions,
+  ]);
+
+  return mergedRule;
+}
+
+module.exports = Object.assign(Components, {
+  detect(rule) {
+    return componentRule.bind(this, rule);
+  },
+});
Index: frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+/**
+ * Checks if we are declaring a `props` argument with a flow type annotation.
+ * @param {ASTNode} node The AST node being checked.
+ * @param {Object} context
+ * @returns {boolean} True if the node is a type annotated props declaration, false if not.
+ */
+export function isAnnotatedFunctionPropsDeclaration(node: ASTNode, context: any): boolean;
+//# sourceMappingURL=annotations.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/annotations.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["annotations.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,0DAJW,OAAO,iBAEL,OAAO,CAenB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/annotations.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/annotations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/annotations.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview Utility functions for type annotation detection.
+ * @author Yannick Croissant
+ * @author Vitor Balocco
+ */
+
+'use strict';
+
+const getFirstTokens = require('./eslint').getFirstTokens;
+
+/**
+ * Checks if we are declaring a `props` argument with a flow type annotation.
+ * @param {ASTNode} node The AST node being checked.
+ * @param {Object} context
+ * @returns {boolean} True if the node is a type annotated props declaration, false if not.
+ */
+function isAnnotatedFunctionPropsDeclaration(node, context) {
+  if (!node || !node.params || !node.params.length) {
+    return false;
+  }
+
+  const typeNode = node.params[0].type === 'AssignmentPattern' ? node.params[0].left : node.params[0];
+
+  const tokens = getFirstTokens(context, typeNode, 2);
+  const isAnnotated = typeNode.typeAnnotation;
+  const isDestructuredProps = typeNode.type === 'ObjectPattern';
+  const isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
+
+  return (isAnnotated && (isDestructuredProps || isProps));
+}
+
+module.exports = {
+  isAnnotatedFunctionPropsDeclaration,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,136 @@
+/**
+ * Find a return statement in the current node
+ *
+ * @param {ASTNode} node The AST node being checked
+ * @returns {ASTNode | false}
+ */
+export function findReturnStatement(node: ASTNode): ASTNode | false;
+/**
+ * Get properties for a given AST node
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {Array} Properties array.
+ */
+export function getComponentProperties(node: ASTNode): any[];
+/**
+ * Gets the first node in a line from the initial node, excluding whitespace.
+ * @param {Object} context The node to check
+ * @param {ASTNode} node The node to check
+ * @return {ASTNode} the first node in the line
+ */
+export function getFirstNodeInLine(context: any, node: ASTNode): ASTNode;
+/**
+ * Retrieve the name of a key node
+ * @param {Context} context The AST node with the key.
+ * @param {any} node The AST node with the key.
+ * @return {string | undefined} the name of the key
+ */
+export function getKeyValue(context: Context, node: any): string | undefined;
+/**
+ * Get properties name
+ * @param {Object} node - Property.
+ * @returns {string} Property name.
+ */
+export function getPropertyName(node: any): string;
+/**
+ * Get node with property's name
+ * @param {Object} node - Property.
+ * @returns {Object} Property name node.
+ */
+export function getPropertyNameNode(node: any): any;
+/**
+ * Check if we are in a class constructor
+ * @param {Context} context
+ * @param {ASTNode} node The AST node being checked.
+ * @return {boolean}
+ */
+export function inConstructor(context: Context, node: ASTNode): boolean;
+/**
+ * Checks if a node is being assigned a value: props.bar = 'bar'
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean}
+ */
+export function isAssignmentLHS(node: ASTNode): boolean;
+/**
+ * Matcher used to check whether given node is a `CallExpression`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `CallExpression`, false if not
+ */
+export function isCallExpression(node: ASTNode): boolean;
+/**
+ * Checks if the node is a class.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a class
+ */
+export function isClass(node: ASTNode): boolean;
+/**
+ * Checks if the node is a function.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function
+ */
+export function isFunction(node: ASTNode): boolean;
+/**
+ * Checks if node is a function declaration or expression or arrow function.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function-like
+ */
+export function isFunctionLike(node: ASTNode): boolean;
+/**
+ * Checks if the node is a function or arrow function expression.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function-like expression
+ */
+export function isFunctionLikeExpression(node: ASTNode): boolean;
+/**
+ * Checks if the node is the first in its line, excluding whitespace.
+ * @param {Object} context The node to check
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's the first node in its line
+ */
+export function isNodeFirstInLine(context: any, node: ASTNode): boolean;
+/**
+ * Checks if a node is surrounded by parenthesis.
+ *
+ * @param {object} context - Context from the rule
+ * @param {ASTNode} node - Node to be checked
+ * @returns {boolean}
+ */
+export function isParenthesized(context: object, node: ASTNode): boolean;
+export function isTSAsExpression(node: any): boolean;
+export function isTSFunctionType(node: any): boolean;
+export function isTSInterfaceDeclaration(node: any): boolean;
+export function isTSInterfaceHeritage(node: any): boolean;
+export function isTSIntersectionType(node: any): boolean;
+export function isTSParenthesizedType(node: any): boolean;
+export function isTSTypeAliasDeclaration(node: any): boolean;
+export function isTSTypeAnnotation(node: any): boolean;
+export function isTSTypeDeclaration(node: any): boolean;
+export function isTSTypeLiteral(node: any): boolean;
+export function isTSTypeParameterInstantiation(node: any): boolean;
+export function isTSTypeQuery(node: any): boolean;
+export function isTSTypeReference(node: any): boolean;
+/**
+ * Wrapper for estraverse.traverse
+ *
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {Object} visitor Visitor Object for estraverse
+ */
+export function traverse(ASTnode: ASTNode, visitor: any): void;
+/**
+ * Helper function for traversing "returns" (return statements or the
+ * returned expression in the case of an arrow function) of a function
+ *
+ * @param {ASTNode} ASTNode The AST node being checked
+ * @param {Context} context The context of `ASTNode`.
+ * @param {(returnValue: ASTNode, breakTraverse: () => void) => void} onReturn
+ *   Function to execute for each returnStatement found
+ * @returns {undefined}
+ */
+export function traverseReturns(ASTNode: ASTNode, context: Context, onReturn: (returnValue: ASTNode, breakTraverse: () => void) => void): undefined;
+/**
+ * Extracts the expression node that is wrapped inside a TS type assertion
+ *
+ * @param {ASTNode} node - potential TS node
+ * @returns {ASTNode} - unwrapped expression node
+ */
+export function unwrapTSAsExpression(node: ASTNode): ASTNode;
+//# sourceMappingURL=ast.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/ast.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["ast.js"],"names":[],"mappings":"AAkDA;;;;;GAKG;AACH,0CAHW,OAAO,GACL,OAAO,GAAG,KAAK,CAa3B;AA0GD;;;;GAIG;AACH,6CAHW,OAAO,SAajB;AAED;;;;;GAKG;AACH,uDAHW,OAAO,GACN,OAAO,CAgBlB;AA8ED;;;;;GAKG;AACH,qCAJW,OAAO,QACP,GAAG,GACF,MAAM,GAAG,SAAS,CAqB7B;AAtJD;;;;GAIG;AACH,4CAFa,MAAM,CAKlB;AA3BD;;;;GAIG;AACH,oDAYC;AAoGD;;;;;GAKG;AACH,uCAJW,OAAO,QACP,OAAO,GACN,OAAO,CAYlB;AAuDD;;;;GAIG;AACH,sCAHW,OAAO,GACL,OAAO,CAQnB;AAMD;;;;GAIG;AACH,uCAHW,OAAO,GACL,OAAO,CAInB;AAxGD;;;;GAIG;AACH,8BAHW,OAAO,GACN,OAAO,CAIlB;AAzBD;;;;GAIG;AACH,iCAHW,OAAO,GACN,OAAO,CAIlB;AAED;;;;GAIG;AACH,qCAHW,OAAO,GACN,OAAO,CAIlB;AAzBD;;;;GAIG;AACH,+CAHW,OAAO,GACN,OAAO,CAIlB;AApBD;;;;;GAKG;AACH,sDAHW,OAAO,GACN,OAAO,CAOlB;AA4FD;;;;;;GAMG;AACH,yCAJW,MAAM,QACN,OAAO,GACL,OAAO,CAUnB;AAeD,qDAEC;AAqFD,qDAIC;AAtCD,6DAOC;AAbD,0DAIC;AAVD,yDAIC;AAoCD,0DAIC;AAbD,6DAOC;AAlDD,uDAIC;AA6BD,wDAQC;AAnCD,oDAIC;AA4DD,mEAIC;AAVD,kDAIC;AA1ED,sDAIC;AAtWD;;;;;GAKG;AACH,kCAHW,OAAO,sBAgBjB;AAqCD;;;;;;;;;GASG;AACH,yCANW,OAAO,WACP,OAAO,0BACO,OAAO,iBAAiB,MAAM,IAAI,KAAK,IAAI,GAEvD,SAAS,CAgErB;AAwND;;;;;GAKG;AACH,2CAHW,OAAO,GACL,OAAO,CAInB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/ast.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/ast.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,483 @@
+/**
+ * @fileoverview Utility functions for AST
+ */
+
+'use strict';
+
+const estraverse = require('estraverse');
+const eslintUtil = require('./eslint');
+
+const getFirstTokens = eslintUtil.getFirstTokens;
+const getScope = eslintUtil.getScope;
+const getSourceCode = eslintUtil.getSourceCode;
+// const pragmaUtil = require('./pragma');
+
+/**
+ * Wrapper for estraverse.traverse
+ *
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {Object} visitor Visitor Object for estraverse
+ */
+function traverse(ASTnode, visitor) {
+  const opts = Object.assign({}, {
+    fallback(node) {
+      return Object.keys(node).filter((key) => key === 'children' || key === 'argument');
+    },
+  }, visitor);
+
+  opts.keys = Object.assign({}, visitor.keys, {
+    JSXElement: ['children'],
+    JSXFragment: ['children'],
+  });
+
+  estraverse.traverse(ASTnode, opts);
+}
+
+function loopNodes(nodes) {
+  for (let i = nodes.length - 1; i >= 0; i--) {
+    if (nodes[i].type === 'ReturnStatement') {
+      return nodes[i];
+    }
+    if (nodes[i].type === 'SwitchStatement') {
+      const j = nodes[i].cases.length - 1;
+      if (j >= 0) {
+        return loopNodes(nodes[i].cases[j].consequent);
+      }
+    }
+  }
+  return false;
+}
+
+/**
+ * Find a return statement in the current node
+ *
+ * @param {ASTNode} node The AST node being checked
+ * @returns {ASTNode | false}
+ */
+function findReturnStatement(node) {
+  if (
+    (!node.value || !node.value.body || !node.value.body.body)
+    && (!node.body || !node.body.body)
+  ) {
+    return false;
+  }
+
+  const bodyNodes = node.value ? node.value.body.body : node.body.body;
+
+  return loopNodes(bodyNodes);
+}
+
+// eslint-disable-next-line valid-jsdoc -- valid-jsdoc cannot parse function types.
+/**
+ * Helper function for traversing "returns" (return statements or the
+ * returned expression in the case of an arrow function) of a function
+ *
+ * @param {ASTNode} ASTNode The AST node being checked
+ * @param {Context} context The context of `ASTNode`.
+ * @param {(returnValue: ASTNode, breakTraverse: () => void) => void} onReturn
+ *   Function to execute for each returnStatement found
+ * @returns {undefined}
+ */
+function traverseReturns(ASTNode, context, onReturn) {
+  const nodeType = ASTNode.type;
+
+  if (nodeType === 'ReturnStatement') {
+    onReturn(ASTNode.argument, () => {});
+    return;
+  }
+
+  if (nodeType === 'ArrowFunctionExpression' && ASTNode.expression) {
+    onReturn(ASTNode.body, () => {});
+    return;
+  }
+
+  /* TODO: properly warn on React.forwardRefs having typo properties
+  if (astUtil.isCallExpression(ASTNode)) {
+    const callee = ASTNode.callee;
+    const pragma = pragmaUtil.getFromContext(context);
+    if (
+      callee.type === 'MemberExpression'
+      && callee.object.type === 'Identifier'
+      && callee.object.name === pragma
+      && callee.property.type === 'Identifier'
+      && callee.property.name === 'forwardRef'
+      && ASTNode.arguments.length > 0
+    ) {
+      return enterFunc(ASTNode.arguments[0]);
+    }
+    return;
+  }
+  */
+
+  if (
+    nodeType !== 'FunctionExpression'
+    && nodeType !== 'FunctionDeclaration'
+    && nodeType !== 'ArrowFunctionExpression'
+    && nodeType !== 'MethodDefinition'
+  ) {
+    return;
+  }
+
+  traverse(ASTNode.body, {
+    enter(node) {
+      const breakTraverse = () => {
+        this.break();
+      };
+      switch (node.type) {
+        case 'ReturnStatement':
+          this.skip();
+          onReturn(node.argument, breakTraverse);
+          return;
+        case 'BlockStatement':
+        case 'IfStatement':
+        case 'ForStatement':
+        case 'WhileStatement':
+        case 'SwitchStatement':
+        case 'SwitchCase':
+          return;
+        default:
+          this.skip();
+      }
+    },
+  });
+}
+
+/**
+ * Get node with property's name
+ * @param {Object} node - Property.
+ * @returns {Object} Property name node.
+ */
+function getPropertyNameNode(node) {
+  if (
+    node.key
+    || node.type === 'MethodDefinition'
+    || node.type === 'Property'
+  ) {
+    return node.key;
+  }
+  if (node.type === 'MemberExpression') {
+    return node.property;
+  }
+  return null;
+}
+
+/**
+ * Get properties name
+ * @param {Object} node - Property.
+ * @returns {string} Property name.
+ */
+function getPropertyName(node) {
+  const nameNode = getPropertyNameNode(node);
+  return nameNode ? nameNode.name : '';
+}
+
+/**
+ * Get properties for a given AST node
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {Array} Properties array.
+ */
+function getComponentProperties(node) {
+  switch (node.type) {
+    case 'ClassDeclaration':
+    case 'ClassExpression':
+      return node.body.body;
+    case 'ObjectExpression':
+      return node.properties;
+    default:
+      return [];
+  }
+}
+
+/**
+ * Gets the first node in a line from the initial node, excluding whitespace.
+ * @param {Object} context The node to check
+ * @param {ASTNode} node The node to check
+ * @return {ASTNode} the first node in the line
+ */
+function getFirstNodeInLine(context, node) {
+  const sourceCode = getSourceCode(context);
+  let token = node;
+  let lines;
+  do {
+    token = sourceCode.getTokenBefore(token);
+    lines = token.type === 'JSXText'
+      ? token.value.split('\n')
+      : null;
+  } while (
+    token.type === 'JSXText'
+        && /^\s*$/.test(lines[lines.length - 1])
+  );
+  return token;
+}
+
+/**
+ * Checks if the node is the first in its line, excluding whitespace.
+ * @param {Object} context The node to check
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's the first node in its line
+ */
+function isNodeFirstInLine(context, node) {
+  const token = getFirstNodeInLine(context, node);
+  const startLine = node.loc.start.line;
+  const endLine = token ? token.loc.end.line : -1;
+  return startLine !== endLine;
+}
+
+/**
+ * Checks if the node is a function or arrow function expression.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function-like expression
+ */
+function isFunctionLikeExpression(node) {
+  return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
+}
+
+/**
+ * Checks if the node is a function.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function
+ */
+function isFunction(node) {
+  return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
+}
+
+/**
+ * Checks if node is a function declaration or expression or arrow function.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a function-like
+ */
+function isFunctionLike(node) {
+  return node.type === 'FunctionDeclaration' || isFunctionLikeExpression(node);
+}
+
+/**
+ * Checks if the node is a class.
+ * @param {ASTNode} node The node to check
+ * @return {boolean} true if it's a class
+ */
+function isClass(node) {
+  return node.type === 'ClassDeclaration' || node.type === 'ClassExpression';
+}
+
+/**
+ * Check if we are in a class constructor
+ * @param {Context} context
+ * @param {ASTNode} node The AST node being checked.
+ * @return {boolean}
+ */
+function inConstructor(context, node) {
+  let scope = getScope(context, node);
+  while (scope) {
+    // @ts-ignore
+    if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
+      return true;
+    }
+    scope = scope.upper;
+  }
+  return false;
+}
+
+/**
+ * Removes quotes from around an identifier.
+ * @param {string} string the identifier to strip
+ * @returns {string}
+ */
+function stripQuotes(string) {
+  return string.replace(/^'|'$/g, '');
+}
+
+/**
+ * Retrieve the name of a key node
+ * @param {Context} context The AST node with the key.
+ * @param {any} node The AST node with the key.
+ * @return {string | undefined} the name of the key
+ */
+function getKeyValue(context, node) {
+  if (node.type === 'ObjectTypeProperty') {
+    const tokens = getFirstTokens(context, node, 2);
+    return (tokens[0].value === '+' || tokens[0].value === '-'
+      ? tokens[1].value
+      : stripQuotes(tokens[0].value)
+    );
+  }
+  if (node.type === 'GenericTypeAnnotation') {
+    return node.id.name;
+  }
+  if (node.type === 'ObjectTypeAnnotation') {
+    return;
+  }
+  const key = node.key || node.argument;
+  if (!key) {
+    return;
+  }
+  return key.type === 'Identifier' ? key.name : key.value;
+}
+
+/**
+ * Checks if a node is surrounded by parenthesis.
+ *
+ * @param {object} context - Context from the rule
+ * @param {ASTNode} node - Node to be checked
+ * @returns {boolean}
+ */
+function isParenthesized(context, node) {
+  const sourceCode = getSourceCode(context);
+  const previousToken = sourceCode.getTokenBefore(node);
+  const nextToken = sourceCode.getTokenAfter(node);
+
+  return !!previousToken && !!nextToken
+    && previousToken.value === '(' && previousToken.range[1] <= node.range[0]
+    && nextToken.value === ')' && nextToken.range[0] >= node.range[1];
+}
+
+/**
+ * Checks if a node is being assigned a value: props.bar = 'bar'
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean}
+ */
+function isAssignmentLHS(node) {
+  return (
+    node.parent
+    && node.parent.type === 'AssignmentExpression'
+    && node.parent.left === node
+  );
+}
+
+function isTSAsExpression(node) {
+  return node && node.type === 'TSAsExpression';
+}
+
+/**
+ * Matcher used to check whether given node is a `CallExpression`
+ * @param {ASTNode} node The AST node
+ * @returns {boolean} True if node is a `CallExpression`, false if not
+ */
+function isCallExpression(node) {
+  return node && node.type === 'CallExpression';
+}
+
+/**
+ * Extracts the expression node that is wrapped inside a TS type assertion
+ *
+ * @param {ASTNode} node - potential TS node
+ * @returns {ASTNode} - unwrapped expression node
+ */
+function unwrapTSAsExpression(node) {
+  return isTSAsExpression(node) ? node.expression : node;
+}
+
+function isTSTypeReference(node) {
+  if (!node) return false;
+
+  return node.type === 'TSTypeReference';
+}
+
+function isTSTypeAnnotation(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSTypeAnnotation';
+}
+
+function isTSTypeLiteral(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSTypeLiteral';
+}
+
+function isTSIntersectionType(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSIntersectionType';
+}
+
+function isTSInterfaceHeritage(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSInterfaceHeritage';
+}
+
+function isTSInterfaceDeclaration(node) {
+  if (!node) { return false; }
+
+  return (node.type === 'ExportNamedDeclaration' && node.declaration
+    ? node.declaration.type
+    : node.type
+  ) === 'TSInterfaceDeclaration';
+}
+
+function isTSTypeDeclaration(node) {
+  if (!node) { return false; }
+
+  const nodeToCheck = node.type === 'ExportNamedDeclaration' && node.declaration
+    ? node.declaration
+    : node;
+
+  return nodeToCheck.type === 'VariableDeclaration' && nodeToCheck.kind === 'type';
+}
+
+function isTSTypeAliasDeclaration(node) {
+  if (!node) { return false; }
+
+  if (node.type === 'ExportNamedDeclaration' && node.declaration) {
+    return node.declaration.type === 'TSTypeAliasDeclaration' && node.exportKind === 'type';
+  }
+  return node.type === 'TSTypeAliasDeclaration';
+}
+
+function isTSParenthesizedType(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSTypeAliasDeclaration';
+}
+
+function isTSFunctionType(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSFunctionType';
+}
+
+function isTSTypeQuery(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSTypeQuery';
+}
+
+function isTSTypeParameterInstantiation(node) {
+  if (!node) { return false; }
+
+  return node.type === 'TSTypeParameterInstantiation';
+}
+
+module.exports = {
+  findReturnStatement,
+  getComponentProperties,
+  getFirstNodeInLine,
+  getKeyValue,
+  getPropertyName,
+  getPropertyNameNode,
+  inConstructor,
+  isAssignmentLHS,
+  isCallExpression,
+  isClass,
+  isFunction,
+  isFunctionLike,
+  isFunctionLikeExpression,
+  isNodeFirstInLine,
+  isParenthesized,
+  isTSAsExpression,
+  isTSFunctionType,
+  isTSInterfaceDeclaration,
+  isTSInterfaceHeritage,
+  isTSIntersectionType,
+  isTSParenthesizedType,
+  isTSTypeAliasDeclaration,
+  isTSTypeAnnotation,
+  isTSTypeDeclaration,
+  isTSTypeLiteral,
+  isTSTypeParameterInstantiation,
+  isTSTypeQuery,
+  isTSTypeReference,
+  traverse,
+  traverseReturns,
+  unwrapTSAsExpression,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+/**
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+export function isES5Component(node: ASTNode, context: Context): boolean;
+/**
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+export function isES6Component(node: ASTNode, context: Context): boolean;
+/**
+ * Get the parent ES5 component node from the current scope
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {ASTNode|null}
+ */
+export function getParentES5Component(context: Context, node: ASTNode): ASTNode | null;
+/**
+ * Get the parent ES6 component node from the current scope
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {ASTNode | null}
+ */
+export function getParentES6Component(context: Context, node: ASTNode): ASTNode | null;
+/**
+ * Check if the node is explicitly declared as a descendant of a React Component
+ * @param {any} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+export function isExplicitComponent(node: any, context: Context): boolean;
+/**
+ * Checks if a component extends React.PureComponent
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+export function isPureComponent(node: ASTNode, context: Context): boolean;
+/**
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+export function isStateMemberExpression(node: ASTNode): boolean;
+//# sourceMappingURL=componentUtil.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"componentUtil.d.ts","sourceRoot":"","sources":["componentUtil.js"],"names":[],"mappings":"AAiCA;;;;GAIG;AACH,qCAJW,OAAO,WACP,OAAO,GACL,OAAO,CAmBnB;AAyCD;;;;GAIG;AACH,qCAJW,OAAO,WACP,OAAO,GACL,OAAO,CAmBnB;AAED;;;;;GAKG;AACH,+CAJW,OAAO,QACP,OAAO,GACL,OAAO,GAAC,IAAI,CAaxB;AAED;;;;;GAKG;AACH,+CAJW,OAAO,QACP,OAAO,GACL,OAAO,GAAG,IAAI,CAY1B;AAlGD;;;;;GAKG;AACH,0CAJW,GAAG,WACH,OAAO,GACL,OAAO,CAiCnB;AA+DD;;;;;GAKG;AACH,sCAJW,OAAO,WACP,OAAO,GACL,OAAO,CAQnB;AAED;;;GAGG;AACH,8CAHW,OAAO,GACL,OAAO,CAMnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,190 @@
+'use strict';
+
+const doctrine = require('doctrine');
+const pragmaUtil = require('./pragma');
+const eslintUtil = require('./eslint');
+
+const getScope = eslintUtil.getScope;
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * @template {(_: object) => any} T
+ * @param {T} fn
+ * @returns {T}
+ */
+function memoize(fn) {
+  const cache = new WeakMap();
+  // @ts-ignore
+  return function memoizedFn(arg) {
+    const cachedValue = cache.get(arg);
+    if (cachedValue !== undefined) {
+      return cachedValue;
+    }
+    const v = fn(arg);
+    cache.set(arg, v);
+    return v;
+  };
+}
+
+const getPragma = memoize(pragmaUtil.getFromContext);
+const getCreateClass = memoize(pragmaUtil.getCreateClassFromContext);
+
+/**
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+function isES5Component(node, context) {
+  const pragma = getPragma(context);
+  const createClass = getCreateClass(context);
+
+  if (!node.parent || !node.parent.callee) {
+    return false;
+  }
+  const callee = node.parent.callee;
+  // React.createClass({})
+  if (callee.type === 'MemberExpression') {
+    return callee.object.name === pragma && callee.property.name === createClass;
+  }
+  // createClass({})
+  if (callee.type === 'Identifier') {
+    return callee.name === createClass;
+  }
+  return false;
+}
+
+/**
+ * Check if the node is explicitly declared as a descendant of a React Component
+ * @param {any} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+function isExplicitComponent(node, context) {
+  const sourceCode = getSourceCode(context);
+  let comment;
+  // Sometimes the passed node may not have been parsed yet by eslint, and this function call crashes.
+  // Can be removed when eslint sets "parent" property for all nodes on initial AST traversal: https://github.com/eslint/eslint-scope/issues/27
+  // eslint-disable-next-line no-warning-comments
+  // FIXME: Remove try/catch when https://github.com/eslint/eslint-scope/issues/27 is implemented.
+  try {
+    comment = sourceCode.getJSDocComment(node);
+  } catch (e) {
+    comment = null;
+  }
+
+  if (comment === null) {
+    return false;
+  }
+
+  let commentAst;
+  try {
+    commentAst = doctrine.parse(comment.value, {
+      unwrap: true,
+      tags: ['extends', 'augments'],
+    });
+  } catch (e) {
+    // handle a bug in the archived `doctrine`, see #2596
+    return false;
+  }
+
+  const relevantTags = commentAst.tags.filter((tag) => tag.name === 'React.Component' || tag.name === 'React.PureComponent');
+
+  return relevantTags.length > 0;
+}
+
+/**
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+function isES6Component(node, context) {
+  const pragma = getPragma(context);
+  if (isExplicitComponent(node, context)) {
+    return true;
+  }
+
+  if (!node.superClass) {
+    return false;
+  }
+  if (node.superClass.type === 'MemberExpression') {
+    return node.superClass.object.name === pragma
+          && /^(Pure)?Component$/.test(node.superClass.property.name);
+  }
+  if (node.superClass.type === 'Identifier') {
+    return /^(Pure)?Component$/.test(node.superClass.name);
+  }
+  return false;
+}
+
+/**
+ * Get the parent ES5 component node from the current scope
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {ASTNode|null}
+ */
+function getParentES5Component(context, node) {
+  let scope = getScope(context, node);
+  while (scope) {
+    // @ts-ignore
+    node = scope.block && scope.block.parent && scope.block.parent.parent;
+    if (node && isES5Component(node, context)) {
+      return node;
+    }
+    scope = scope.upper;
+  }
+  return null;
+}
+
+/**
+ * Get the parent ES6 component node from the current scope
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {ASTNode | null}
+ */
+function getParentES6Component(context, node) {
+  let scope = getScope(context, node);
+  while (scope && scope.type !== 'class') {
+    scope = scope.upper;
+  }
+  node = scope && scope.block;
+  if (!node || !isES6Component(node, context)) {
+    return null;
+  }
+  return node;
+}
+
+/**
+ * Checks if a component extends React.PureComponent
+ * @param {ASTNode} node
+ * @param {Context} context
+ * @returns {boolean}
+ */
+function isPureComponent(node, context) {
+  const pragma = getPragma(context);
+  if (node.superClass) {
+    return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(getText(context, node.superClass));
+  }
+  return false;
+}
+
+/**
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isStateMemberExpression(node) {
+  return node.type === 'MemberExpression'
+    && node.object.type === 'ThisExpression'
+    && node.property.name === 'state';
+}
+
+module.exports = {
+  isES5Component,
+  isES6Component,
+  getParentES5Component,
+  getParentES6Component,
+  isExplicitComponent,
+  isPureComponent,
+  isStateMemberExpression,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+declare function _exports(context: any, components: any, utils: any): {
+    MemberExpression(node: any): void;
+    MethodDefinition(node: any): void;
+    'ClassProperty, PropertyDefinition'(node: any): void;
+    ObjectExpression(node: any): void;
+};
+export = _exports;
+//# sourceMappingURL=defaultProps.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"defaultProps.d.ts","sourceRoot":"","sources":["defaultProps.js"],"names":[],"mappings":"AAgBiB;;;;;EA0PhB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/defaultProps.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,267 @@
+/**
+ * @fileoverview Common defaultProps detection functionality.
+ */
+
+'use strict';
+
+const fromEntries = require('object.fromentries');
+const astUtil = require('./ast');
+const componentUtil = require('./componentUtil');
+const propsUtil = require('./props');
+const variableUtil = require('./variable');
+const propWrapperUtil = require('./propWrapper');
+const getText = require('./eslint').getText;
+
+const QUOTES_REGEX = /^["']|["']$/g;
+
+module.exports = function defaultPropsInstructions(context, components, utils) {
+  /**
+   * Try to resolve the node passed in to a variable in the current scope. If the node passed in is not
+   * an Identifier, then the node is simply returned.
+   * @param   {ASTNode} node The node to resolve.
+   * @returns {ASTNode|null} Return null if the value could not be resolved, ASTNode otherwise.
+   */
+  function resolveNodeValue(node) {
+    if (node.type === 'Identifier') {
+      return variableUtil.findVariableByName(context, node, node.name);
+    }
+    if (
+      astUtil.isCallExpression(node)
+      && propWrapperUtil.isPropWrapperFunction(context, node.callee.name)
+      && node.arguments && node.arguments[0]
+    ) {
+      return resolveNodeValue(node.arguments[0]);
+    }
+    return node;
+  }
+
+  /**
+   * Extracts a DefaultProp from an ObjectExpression node.
+   * @param   {ASTNode} objectExpression ObjectExpression node.
+   * @returns {Object|string}            Object representation of a defaultProp, to be consumed by
+   *                                     `addDefaultPropsToComponent`, or string "unresolved", if the defaultProps
+   *                                     from this ObjectExpression can't be resolved.
+   */
+  function getDefaultPropsFromObjectExpression(objectExpression) {
+    const hasSpread = objectExpression.properties.find((property) => property.type === 'ExperimentalSpreadProperty' || property.type === 'SpreadElement');
+
+    if (hasSpread) {
+      return 'unresolved';
+    }
+
+    return objectExpression.properties.map((defaultProp) => ({
+      name: getText(context, defaultProp.key).replace(QUOTES_REGEX, ''),
+      node: defaultProp,
+    }));
+  }
+
+  /**
+   * Marks a component's DefaultProps declaration as "unresolved". A component's DefaultProps is
+   * marked as "unresolved" if we cannot safely infer the values of its defaultProps declarations
+   * without risking false negatives.
+   * @param   {Object} component The component to mark.
+   * @returns {void}
+   */
+  function markDefaultPropsAsUnresolved(component) {
+    components.set(component.node, {
+      defaultProps: 'unresolved',
+    });
+  }
+
+  /**
+   * Adds defaultProps to the component passed in.
+   * @param   {ASTNode}         component    The component to add the defaultProps to.
+   * @param   {Object[]|'unresolved'} defaultProps defaultProps to add to the component or the string "unresolved"
+   *                                         if this component has defaultProps that can't be resolved.
+   * @returns {void}
+   */
+  function addDefaultPropsToComponent(component, defaultProps) {
+    // Early return if this component's defaultProps is already marked as "unresolved".
+    if (component.defaultProps === 'unresolved') {
+      return;
+    }
+
+    if (defaultProps === 'unresolved') {
+      markDefaultPropsAsUnresolved(component);
+      return;
+    }
+
+    const defaults = component.defaultProps || {};
+    const newDefaultProps = Object.assign(
+      {},
+      defaults,
+      fromEntries(defaultProps.map((prop) => [prop.name, prop]))
+    );
+
+    components.set(component.node, {
+      defaultProps: newDefaultProps,
+    });
+  }
+
+  return {
+    MemberExpression(node) {
+      const isDefaultProp = propsUtil.isDefaultPropsDeclaration(node);
+
+      if (!isDefaultProp) {
+        return;
+      }
+
+      // find component this defaultProps belongs to
+      const component = utils.getRelatedComponent(node);
+      if (!component) {
+        return;
+      }
+
+      // e.g.:
+      // MyComponent.propTypes = {
+      //   foo: React.PropTypes.string.isRequired,
+      //   bar: React.PropTypes.string
+      // };
+      //
+      // or:
+      //
+      // MyComponent.propTypes = myPropTypes;
+      if (node.parent.type === 'AssignmentExpression') {
+        const expression = resolveNodeValue(node.parent.right);
+        if (!expression || expression.type !== 'ObjectExpression') {
+          // If a value can't be found, we mark the defaultProps declaration as "unresolved", because
+          // we should ignore this component and not report any errors for it, to avoid false-positives
+          // with e.g. external defaultProps declarations.
+          if (isDefaultProp) {
+            markDefaultPropsAsUnresolved(component);
+          }
+
+          return;
+        }
+
+        addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
+
+        return;
+      }
+
+      // e.g.:
+      // MyComponent.propTypes.baz = React.PropTypes.string;
+      if (node.parent.type === 'MemberExpression' && node.parent.parent
+        && node.parent.parent.type === 'AssignmentExpression') {
+        addDefaultPropsToComponent(component, [{
+          name: node.parent.property.name,
+          node: node.parent.parent,
+        }]);
+      }
+    },
+
+    // e.g.:
+    // class Hello extends React.Component {
+    //   static get defaultProps() {
+    //     return {
+    //       name: 'Dean'
+    //     };
+    //   }
+    //   render() {
+    //     return <div>Hello {this.props.name}</div>;
+    //   }
+    // }
+    MethodDefinition(node) {
+      if (!node.static || node.kind !== 'get') {
+        return;
+      }
+
+      if (!propsUtil.isDefaultPropsDeclaration(node)) {
+        return;
+      }
+
+      // find component this propTypes/defaultProps belongs to
+      const component = components.get(componentUtil.getParentES6Component(context, node));
+      if (!component) {
+        return;
+      }
+
+      const returnStatement = utils.findReturnStatement(node);
+      if (!returnStatement) {
+        return;
+      }
+
+      const expression = resolveNodeValue(returnStatement.argument);
+      if (!expression || expression.type !== 'ObjectExpression') {
+        return;
+      }
+
+      addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
+    },
+
+    // e.g.:
+    // class Greeting extends React.Component {
+    //   render() {
+    //     return (
+    //       <h1>Hello, {this.props.foo} {this.props.bar}</h1>
+    //     );
+    //   }
+    //   static defaultProps = {
+    //     foo: 'bar',
+    //     bar: 'baz'
+    //   };
+    // }
+    'ClassProperty, PropertyDefinition'(node) {
+      if (!(node.static && node.value)) {
+        return;
+      }
+
+      const propName = astUtil.getPropertyName(node);
+      const isDefaultProp = propName === 'defaultProps' || propName === 'getDefaultProps';
+
+      if (!isDefaultProp) {
+        return;
+      }
+
+      // find component this propTypes/defaultProps belongs to
+      const component = components.get(componentUtil.getParentES6Component(context, node));
+      if (!component) {
+        return;
+      }
+
+      const expression = resolveNodeValue(node.value);
+      if (!expression || expression.type !== 'ObjectExpression') {
+        return;
+      }
+
+      addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(expression));
+    },
+
+    // e.g.:
+    // React.createClass({
+    //   render: function() {
+    //     return <div>{this.props.foo}</div>;
+    //   },
+    //   getDefaultProps: function() {
+    //     return {
+    //       foo: 'default'
+    //     };
+    //   }
+    // });
+    ObjectExpression(node) {
+      // find component this propTypes/defaultProps belongs to
+      const component = componentUtil.isES5Component(node, context) && components.get(node);
+      if (!component) {
+        return;
+      }
+
+      // Search for the proptypes declaration
+      node.properties.forEach((property) => {
+        if (property.type === 'ExperimentalSpreadProperty' || property.type === 'SpreadElement') {
+          return;
+        }
+
+        const isDefaultProp = propsUtil.isDefaultPropsDeclaration(property);
+
+        if (isDefaultProp && property.value.type === 'FunctionExpression') {
+          const returnStatement = utils.findReturnStatement(property);
+          if (!returnStatement || returnStatement.argument.type !== 'ObjectExpression') {
+            return;
+          }
+
+          addDefaultPropsToComponent(component, getDefaultPropsFromObjectExpression(returnStatement.argument));
+        }
+      });
+    },
+  };
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+export = docsUrl;
+declare function docsUrl(ruleName: any): string;
+//# sourceMappingURL=docsUrl.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"docsUrl.d.ts","sourceRoot":"","sources":["docsUrl.js"],"names":[],"mappings":";AAEA,gDAEC"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/docsUrl.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+'use strict';
+
+function docsUrl(ruleName) {
+  return `https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/${ruleName}.md`;
+}
+
+module.exports = docsUrl;
Index: frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+export = error;
+/**
+ * Logs out a message if there is no format option set.
+ * @param {string} message - Message to log.
+ */
+declare function error(message: string): void;
+//# sourceMappingURL=error.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/error.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["error.js"],"names":[],"mappings":";AAEA;;;GAGG;AACH,gCAFW,MAAM,QAOhB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/error.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/error.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/error.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Logs out a message if there is no format option set.
+ * @param {string} message - Message to log.
+ */
+function error(message) {
+  if (!/=-(f|-format)=/.test(process.argv.join('='))) {
+    // eslint-disable-next-line no-console
+    console.error(message);
+  }
+}
+
+module.exports = error;
Index: frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+export function getAncestors(context: any, node: any): any;
+export function getFirstTokens(context: any, node: any, count: any): any;
+export function getScope(context: any, node: any): any;
+export function getSourceCode(context: any): any;
+export function getText(context: any, ...args: any[]): any;
+export function markVariableAsUsed(name: any, node: any, context: any): any;
+//# sourceMappingURL=eslint.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/eslint.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"eslint.d.ts","sourceRoot":"","sources":["eslint.js"],"names":[],"mappings":"AAMA,2DAGC;AAkBD,yEAGC;AAnBD,uDAOC;AAhBD,iDAEC;AA4BD,2DAIC;AAhBD,4EAKC"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/eslint.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/eslint.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,46 @@
+'use strict';
+
+function getSourceCode(context) {
+  return context.getSourceCode ? context.getSourceCode() : context.sourceCode;
+}
+
+function getAncestors(context, node) {
+  const sourceCode = getSourceCode(context);
+  return sourceCode.getAncestors ? sourceCode.getAncestors(node) : context.getAncestors();
+}
+
+function getScope(context, node) {
+  const sourceCode = getSourceCode(context);
+  if (sourceCode.getScope) {
+    return sourceCode.getScope(node);
+  }
+
+  return context.getScope();
+}
+
+function markVariableAsUsed(name, node, context) {
+  const sourceCode = getSourceCode(context);
+  return sourceCode.markVariableAsUsed
+    ? sourceCode.markVariableAsUsed(name, node)
+    : context.markVariableAsUsed(name);
+}
+
+function getFirstTokens(context, node, count) {
+  const sourceCode = getSourceCode(context);
+  return sourceCode.getFirstTokens ? sourceCode.getFirstTokens(node, count) : context.getFirstTokens(node, count);
+}
+
+function getText(context) {
+  const sourceCode = getSourceCode(context);
+  const args = Array.prototype.slice.call(arguments, 1);
+  return sourceCode.getText ? sourceCode.getText.apply(sourceCode, args) : context.getSource.apply(context, args);
+}
+
+module.exports = {
+  getAncestors,
+  getFirstTokens,
+  getScope,
+  getSourceCode,
+  getText,
+  markVariableAsUsed,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+export = getTokenBeforeClosingBracket;
+/**
+ * Find the token before the closing bracket.
+ * @param {ASTNode} node - The JSX element node.
+ * @returns {Token} The token before the closing bracket.
+ */
+declare function getTokenBeforeClosingBracket(node: ASTNode): Token;
+//# sourceMappingURL=getTokenBeforeClosingBracket.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"getTokenBeforeClosingBracket.d.ts","sourceRoot":"","sources":["getTokenBeforeClosingBracket.js"],"names":[],"mappings":";AAEA;;;;GAIG;AACH,oDAHW,OAAO,GACL,KAAK,CAQjB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ * Find the token before the closing bracket.
+ * @param {ASTNode} node - The JSX element node.
+ * @returns {Token} The token before the closing bracket.
+ */
+function getTokenBeforeClosingBracket(node) {
+  const attributes = node.attributes;
+  if (!attributes || attributes.length === 0) {
+    return node.name;
+  }
+  return attributes[attributes.length - 1];
+}
+
+module.exports = getTokenBeforeClosingBracket;
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(node: ASTNode): boolean;
+export = _exports;
+//# sourceMappingURL=isCreateContext.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"isCreateContext.d.ts","sourceRoot":"","sources":["isCreateContext.js"],"names":[],"mappings":"AASiB,gCAHN,OAAO,GACL,OAAO,CA8CnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateContext.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+'use strict';
+
+const astUtil = require('./ast');
+
+/**
+ * Checks if the node is a React.createContext call
+ * @param {ASTNode} node - The AST node being checked.
+ * @returns {boolean} - True if node is a React.createContext call, false if not.
+ */
+module.exports = function isCreateContext(node) {
+  if (
+    node.init
+    && node.init.callee
+  ) {
+    if (
+      astUtil.isCallExpression(node.init)
+      && node.init.callee.name === 'createContext'
+    ) {
+      return true;
+    }
+
+    if (
+      node.init.callee.type === 'MemberExpression'
+      && node.init.callee.property
+      && node.init.callee.property.name === 'createContext'
+    ) {
+      return true;
+    }
+  }
+
+  if (
+    node.expression
+    && node.expression.type === 'AssignmentExpression'
+    && node.expression.operator === '='
+    && astUtil.isCallExpression(node.expression.right)
+    && node.expression.right.callee
+  ) {
+    const right = node.expression.right;
+
+    if (right.callee.name === 'createContext') {
+      return true;
+    }
+
+    if (
+      right.callee.type === 'MemberExpression'
+      && right.callee.property
+      && right.callee.property.name === 'createContext'
+    ) {
+      return true;
+    }
+  }
+
+  return false;
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(context: Context, node: ASTNode): boolean;
+export = _exports;
+//# sourceMappingURL=isCreateElement.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"isCreateElement.d.ts","sourceRoot":"","sources":["isCreateElement.js"],"names":[],"mappings":"AAWiB,mCAJN,OAAO,QACP,OAAO,GACL,OAAO,CAwBnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isCreateElement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,34 @@
+'use strict';
+
+const pragmaUtil = require('./pragma');
+const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');
+
+/**
+ * Checks if the node is a createElement call
+ * @param {Context} context - The AST node being checked.
+ * @param {ASTNode} node - The AST node being checked.
+ * @returns {boolean} - True if node is a createElement call object literal, False if not.
+*/
+module.exports = function isCreateElement(context, node) {
+  if (!node.callee) {
+    return false;
+  }
+
+  if (
+    node.callee.type === 'MemberExpression'
+    && node.callee.property.name === 'createElement'
+    && node.callee.object
+    && node.callee.object.name === pragmaUtil.getFromContext(context)
+  ) {
+    return true;
+  }
+
+  if (
+    node.callee.name === 'createElement'
+    && isDestructuredFromPragmaImport(context, node, 'createElement')
+  ) {
+    return true;
+  }
+
+  return false;
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(context: Context, node: ASTNode, variable: string, ...args: any[]): boolean;
+export = _exports;
+//# sourceMappingURL=isDestructuredFromPragmaImport.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"isDestructuredFromPragmaImport.d.ts","sourceRoot":"","sources":["isDestructuredFromPragmaImport.js"],"names":[],"mappings":"AAciB,mCALN,OAAO,QACP,OAAO,YACP,MAAM,mBACJ,OAAO,CAmEnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isDestructuredFromPragmaImport.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,80 @@
+'use strict';
+
+const astUtil = require('./ast');
+const pragmaUtil = require('./pragma');
+const variableUtil = require('./variable');
+
+/**
+ * Check if variable is destructured from pragma import
+ *
+ * @param {Context} context eslint context
+ * @param {ASTNode} node The AST node to check
+ * @param {string} variable The variable name to check
+ * @returns {boolean} True if createElement is destructured from the pragma
+ */
+module.exports = function isDestructuredFromPragmaImport(context, node, variable) {
+  const pragma = pragmaUtil.getFromContext(context);
+  const variableInScope = variableUtil.getVariableFromContext(context, node, variable);
+  if (variableInScope) {
+    const latestDef = variableUtil.getLatestVariableDefinition(variableInScope);
+    if (latestDef) {
+      // check if latest definition is a variable declaration: 'variable = value'
+      if (latestDef.node.type === 'VariableDeclarator' && latestDef.node.init) {
+        // check for: 'variable = pragma.variable'
+        if (
+          latestDef.node.init.type === 'MemberExpression'
+          && latestDef.node.init.object.type === 'Identifier'
+          && latestDef.node.init.object.name === pragma
+        ) {
+          return true;
+        }
+        // check for: '{variable} = pragma'
+        if (
+          latestDef.node.init.type === 'Identifier'
+          && latestDef.node.init.name === pragma
+        ) {
+          return true;
+        }
+
+        // "require('react')"
+        let requireExpression = null;
+
+        // get "require('react')" from: "{variable} = require('react')"
+        if (astUtil.isCallExpression(latestDef.node.init)) {
+          requireExpression = latestDef.node.init;
+        }
+        // get "require('react')" from: "variable = require('react').variable"
+        if (
+          !requireExpression
+          && latestDef.node.init.type === 'MemberExpression'
+          && astUtil.isCallExpression(latestDef.node.init.object)
+        ) {
+          requireExpression = latestDef.node.init.object;
+        }
+
+        // check proper require.
+        if (
+          requireExpression
+          && requireExpression.callee
+          && requireExpression.callee.name === 'require'
+          && requireExpression.arguments[0]
+          && requireExpression.arguments[0].value === pragma.toLocaleLowerCase()
+        ) {
+          return true;
+        }
+
+        return false;
+      }
+
+      // latest definition is an import declaration: import {<variable>} from 'react'
+      if (
+        latestDef.parent
+        && latestDef.parent.type === 'ImportDeclaration'
+        && latestDef.parent.source.value === pragma.toLocaleLowerCase()
+      ) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(word: string): boolean;
+export = _exports;
+//# sourceMappingURL=isFirstLetterCapitalized.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"isFirstLetterCapitalized.d.ts","sourceRoot":"","sources":["isFirstLetterCapitalized.js"],"names":[],"mappings":"AAOiB,gCAHN,MAAM,GACJ,OAAO,CAQnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/isFirstLetterCapitalized.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Check if the first letter of a string is capitalized.
+ * @param {string} word String to check
+ * @returns {boolean} True if first letter is capitalized.
+ */
+module.exports = function isFirstLetterCapitalized(word) {
+  if (!word) {
+    return false;
+  }
+  const firstLetter = word.replace(/^_+/, '').charAt(0);
+  return firstLetter.toUpperCase() === firstLetter;
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,51 @@
+/**
+ * Checks if a node represents a DOM element according to React.
+ * @param {object} node - JSXOpeningElement to check.
+ * @returns {boolean} Whether or not the node corresponds to a DOM element.
+ */
+export function isDOMComponent(node: object): boolean;
+/**
+ * Test whether a JSXElement is a fragment
+ * @param {JSXElement} node
+ * @param {string} reactPragma
+ * @param {string} fragmentPragma
+ * @returns {boolean}
+ */
+export function isFragment(node: JSXElement, reactPragma: string, fragmentPragma: string): boolean;
+/**
+ * Checks if a node represents a JSX element or fragment.
+ * @param {object} node - node to check.
+ * @returns {boolean} Whether or not the node if a JSX element or fragment.
+ */
+export function isJSX(node: object): boolean;
+/**
+ * Check if node is like `key={...}` as in `<Foo key={...} />`
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+export function isJSXAttributeKey(node: ASTNode): boolean;
+/**
+ * Check if value has only whitespaces
+ * @param {unknown} value
+ * @returns {boolean}
+ */
+export function isWhiteSpaces(value: unknown): boolean;
+/**
+ * Check if the node is returning JSX or null
+ *
+ * @param {Context} context The context of `ASTNode`.
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {boolean} [strict] If true, in a ternary condition the node must return JSX in both cases
+ * @param {boolean} [ignoreNull] If true, null return values will be ignored
+ * @returns {boolean} True if the node is returning JSX or null, false if not
+ */
+export function isReturningJSX(context: Context, ASTnode: ASTNode, strict?: boolean, ignoreNull?: boolean): boolean;
+/**
+ * Check if the node is returning only null values
+ *
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {Context} context The context of `ASTNode`.
+ * @returns {boolean} True if the node is returning only null values
+ */
+export function isReturningOnlyNull(ASTnode: ASTNode, context: Context): boolean;
+//# sourceMappingURL=jsx.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/jsx.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"jsx.d.ts","sourceRoot":"","sources":["jsx.js"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAKnB;AAED;;;;;;GAMG;AACH,iCALW,UAAU,eACV,MAAM,kBACN,MAAM,GACJ,OAAO,CAsBnB;AAED;;;;GAIG;AACH,4BAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;GAIG;AACH,wCAHW,OAAO,GACL,OAAO,CAOnB;AAED;;;;GAIG;AACH,qCAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;;;;;GAQG;AACH,wCANW,OAAO,WACP,OAAO,WACP,OAAO,eACP,OAAO,GACL,OAAO,CAgDnB;AAED;;;;;;GAMG;AACH,6CAJW,OAAO,WACP,OAAO,GACL,OAAO,CAsCnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/jsx.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/jsx.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/jsx.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,196 @@
+/**
+ * @fileoverview Utility functions for JSX
+ */
+
+'use strict';
+
+const elementType = require('jsx-ast-utils/elementType');
+
+const astUtil = require('./ast');
+const isCreateElement = require('./isCreateElement');
+const variableUtil = require('./variable');
+
+// See https://github.com/babel/babel/blob/ce420ba51c68591e057696ef43e028f41c6e04cd/packages/babel-types/src/validators/react/isCompatTag.js
+// for why we only test for the first character
+const COMPAT_TAG_REGEX = /^[a-z]/;
+
+/**
+ * Checks if a node represents a DOM element according to React.
+ * @param {object} node - JSXOpeningElement to check.
+ * @returns {boolean} Whether or not the node corresponds to a DOM element.
+ */
+function isDOMComponent(node) {
+  const name = elementType(node);
+  return COMPAT_TAG_REGEX.test(name);
+}
+
+/**
+ * Test whether a JSXElement is a fragment
+ * @param {JSXElement} node
+ * @param {string} reactPragma
+ * @param {string} fragmentPragma
+ * @returns {boolean}
+ */
+function isFragment(node, reactPragma, fragmentPragma) {
+  const name = node.openingElement.name;
+
+  // <Fragment>
+  if (name.type === 'JSXIdentifier' && name.name === fragmentPragma) {
+    return true;
+  }
+
+  // <React.Fragment>
+  if (
+    name.type === 'JSXMemberExpression'
+    && name.object.type === 'JSXIdentifier'
+    && name.object.name === reactPragma
+    && name.property.type === 'JSXIdentifier'
+    && name.property.name === fragmentPragma
+  ) {
+    return true;
+  }
+
+  return false;
+}
+
+/**
+ * Checks if a node represents a JSX element or fragment.
+ * @param {object} node - node to check.
+ * @returns {boolean} Whether or not the node if a JSX element or fragment.
+ */
+function isJSX(node) {
+  return node && ['JSXElement', 'JSXFragment'].indexOf(node.type) >= 0;
+}
+
+/**
+ * Check if node is like `key={...}` as in `<Foo key={...} />`
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isJSXAttributeKey(node) {
+  return node.type === 'JSXAttribute'
+    && node.name
+    && node.name.type === 'JSXIdentifier'
+    && node.name.name === 'key';
+}
+
+/**
+ * Check if value has only whitespaces
+ * @param {unknown} value
+ * @returns {boolean}
+ */
+function isWhiteSpaces(value) {
+  return typeof value === 'string' ? /^\s*$/.test(value) : false;
+}
+
+/**
+ * Check if the node is returning JSX or null
+ *
+ * @param {Context} context The context of `ASTNode`.
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {boolean} [strict] If true, in a ternary condition the node must return JSX in both cases
+ * @param {boolean} [ignoreNull] If true, null return values will be ignored
+ * @returns {boolean} True if the node is returning JSX or null, false if not
+ */
+function isReturningJSX(context, ASTnode, strict, ignoreNull) {
+  const isJSXValue = (node) => {
+    if (!node) {
+      return false;
+    }
+    switch (node.type) {
+      case 'ConditionalExpression':
+        if (strict) {
+          return isJSXValue(node.consequent) && isJSXValue(node.alternate);
+        }
+        return isJSXValue(node.consequent) || isJSXValue(node.alternate);
+      case 'LogicalExpression':
+        if (strict) {
+          return isJSXValue(node.left) && isJSXValue(node.right);
+        }
+        return isJSXValue(node.left) || isJSXValue(node.right);
+      case 'SequenceExpression':
+        return isJSXValue(node.expressions[node.expressions.length - 1]);
+      case 'JSXElement':
+      case 'JSXFragment':
+        return true;
+      case 'CallExpression':
+        return isCreateElement(context, node);
+      case 'Literal':
+        if (!ignoreNull && node.value === null) {
+          return true;
+        }
+        return false;
+      case 'Identifier': {
+        const variable = variableUtil.findVariableByName(context, node, node.name);
+        return isJSX(variable);
+      }
+      default:
+        return false;
+    }
+  };
+
+  let found = false;
+  astUtil.traverseReturns(ASTnode, context, (node, breakTraverse) => {
+    if (isJSXValue(node)) {
+      found = true;
+      breakTraverse();
+    }
+  });
+
+  return found;
+}
+
+/**
+ * Check if the node is returning only null values
+ *
+ * @param {ASTNode} ASTnode The AST node being checked
+ * @param {Context} context The context of `ASTNode`.
+ * @returns {boolean} True if the node is returning only null values
+ */
+function isReturningOnlyNull(ASTnode, context) {
+  let found = false;
+  let foundSomethingElse = false;
+  astUtil.traverseReturns(ASTnode, context, (node) => {
+    // Traverse return statement
+    astUtil.traverse(node, {
+      enter(childNode) {
+        const setFound = () => {
+          found = true;
+          this.skip();
+        };
+        const setFoundSomethingElse = () => {
+          foundSomethingElse = true;
+          this.skip();
+        };
+        switch (childNode.type) {
+          case 'ReturnStatement':
+            break;
+          case 'ConditionalExpression':
+            if (childNode.consequent.value === null && childNode.alternate.value === null) {
+              setFound();
+            }
+            break;
+          case 'Literal':
+            if (childNode.value === null) {
+              setFound();
+            }
+            break;
+          default:
+            setFoundSomethingElse();
+        }
+      },
+    });
+  });
+
+  return found && !foundSomethingElse;
+}
+
+module.exports = {
+  isDOMComponent,
+  isFragment,
+  isJSX,
+  isJSXAttributeKey,
+  isWhiteSpaces,
+  isReturningJSX,
+  isReturningOnlyNull,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,6 @@
+declare const _exports: {
+    instance: string[];
+    static: string[];
+};
+export = _exports;
+//# sourceMappingURL=lifecycleMethods.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"lifecycleMethods.d.ts","sourceRoot":"","sources":["lifecycleMethods.js"],"names":[],"mappings":""}
Index: frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/lifecycleMethods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview lifecycle methods
+ * @author Tan Nguyen
+ */
+
+'use strict';
+
+module.exports = {
+  instance: [
+    'getDefaultProps',
+    'getInitialState',
+    'getChildContext',
+    'componentWillMount',
+    'UNSAFE_componentWillMount',
+    'componentDidMount',
+    'componentWillReceiveProps',
+    'UNSAFE_componentWillReceiveProps',
+    'shouldComponentUpdate',
+    'componentWillUpdate',
+    'UNSAFE_componentWillUpdate',
+    'getSnapshotBeforeUpdate',
+    'componentDidUpdate',
+    'componentDidCatch',
+    'componentWillUnmount',
+    'render',
+  ],
+  static: [
+    'getDerivedStateFromProps',
+  ],
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+export function getFormComponents(context: any): Map<any, any>;
+export function getLinkComponents(context: any): Map<any, any>;
+//# sourceMappingURL=linkComponents.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"linkComponents.d.ts","sourceRoot":"","sources":["linkComponents.js"],"names":[],"mappings":"AAmBA,+DAWC;AAED,+DAWC"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/linkComponents.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Utility functions for propWrapperFunctions setting
+ */
+
+'use strict';
+
+const iterFrom = require('es-iterator-helpers/Iterator.from');
+const map = require('es-iterator-helpers/Iterator.prototype.map');
+
+/** TODO: type {(string | { name: string, linkAttribute: string })[]} */
+/** @type {any} */
+const DEFAULT_LINK_COMPONENTS = ['a'];
+const DEFAULT_LINK_ATTRIBUTE = 'href';
+
+/** TODO: type {(string | { name: string, formAttribute: string })[]} */
+/** @type {any} */
+const DEFAULT_FORM_COMPONENTS = ['form'];
+const DEFAULT_FORM_ATTRIBUTE = 'action';
+
+function getFormComponents(context) {
+  const settings = context.settings || {};
+  const formComponents = /** @type {typeof DEFAULT_FORM_COMPONENTS} */ (
+    DEFAULT_FORM_COMPONENTS.concat(settings.formComponents || [])
+  );
+  return new Map(map(iterFrom(formComponents), (value) => {
+    if (typeof value === 'string') {
+      return [value, [DEFAULT_FORM_ATTRIBUTE]];
+    }
+    return [value.name, [].concat(value.formAttribute)];
+  }));
+}
+
+function getLinkComponents(context) {
+  const settings = context.settings || {};
+  const linkComponents = /** @type {typeof DEFAULT_LINK_COMPONENTS} */ (
+    DEFAULT_LINK_COMPONENTS.concat(settings.linkComponents || [])
+  );
+  return new Map(map(iterFrom(linkComponents), (value) => {
+    if (typeof value === 'string') {
+      return [value, [DEFAULT_LINK_ATTRIBUTE]];
+    }
+    return [value.name, [].concat(value.linkAttribute)];
+  }));
+}
+
+module.exports = {
+  getFormComponents,
+  getLinkComponents,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,7 @@
+export = log;
+/**
+ * Logs out a message if there is no format option set.
+ * @param {string} message - Message to log.
+ */
+declare function log(message: string): void;
+//# sourceMappingURL=log.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/log.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["log.js"],"names":[],"mappings":";AAEA;;;GAGG;AACH,8BAFW,MAAM,QAOhB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/log.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/log.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/log.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Logs out a message if there is no format option set.
+ * @param {string} message - Message to log.
+ */
+function log(message) {
+  if (!/=-(f|-format)=/.test(process.argv.join('='))) {
+    // eslint-disable-next-line no-console
+    console.log(message);
+  }
+}
+
+module.exports = log;
Index: frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(methodName: string, shouldCheckUnsafeCb?: (context: import('eslint').Rule.RuleContext) => boolean): import('eslint').Rule.RuleModule;
+export = _exports;
+//# sourceMappingURL=makeNoMethodSetStateRule.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"makeNoMethodSetStateRule.d.ts","sourceRoot":"","sources":["makeNoMethodSetStateRule.js"],"names":[],"mappings":"AAoDiB,sCAJN,MAAM,kCACI,OAAO,QAAQ,EAAE,IAAI,CAAC,WAAW,KAAK,OAAO,GACrD,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU,CA+E5C"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/makeNoMethodSetStateRule.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,130 @@
+/**
+ * @fileoverview Prevent usage of setState in lifecycle methods
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const findLast = require('array.prototype.findlast');
+
+const docsUrl = require('./docsUrl');
+const report = require('./report');
+const getAncestors = require('./eslint').getAncestors;
+const testReactVersion = require('./version').testReactVersion;
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+function mapTitle(methodName) {
+  const map = {
+    componentDidMount: 'did-mount',
+    componentDidUpdate: 'did-update',
+    componentWillUpdate: 'will-update',
+  };
+  const title = map[methodName];
+  if (!title) {
+    throw Error(`No docsUrl for '${methodName}'`);
+  }
+  return `no-${title}-set-state`;
+}
+
+const messages = {
+  noSetState: 'Do not use setState in {{name}}',
+};
+
+const methodNoopsAsOf = {
+  componentDidMount: '>= 16.3.0',
+  componentDidUpdate: '>= 16.3.0',
+};
+
+function shouldBeNoop(context, methodName) {
+  return methodName in methodNoopsAsOf
+    && testReactVersion(context, methodNoopsAsOf[methodName])
+    && !testReactVersion(context, '999.999.999'); // for when the version is not specified
+}
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * @param {string} methodName
+ * @param {(context: import('eslint').Rule.RuleContext) => boolean} [shouldCheckUnsafeCb]
+ * @returns {import('eslint').Rule.RuleModule}
+ */
+module.exports = function makeNoMethodSetStateRule(methodName, shouldCheckUnsafeCb) {
+  return {
+    meta: {
+      docs: {
+        description: `Disallow usage of setState in ${methodName}`,
+        category: 'Best Practices',
+        recommended: false,
+        url: docsUrl(mapTitle(methodName)),
+      },
+
+      messages,
+
+      schema: [{
+        enum: ['disallow-in-func'],
+      }],
+    },
+
+    create(context) {
+      const mode = context.options[0] || 'allow-in-func';
+
+      function nameMatches(name) {
+        if (name === methodName) {
+          return true;
+        }
+
+        if (typeof shouldCheckUnsafeCb === 'function' && shouldCheckUnsafeCb(context)) {
+          return name === `UNSAFE_${methodName}`;
+        }
+
+        return false;
+      }
+
+      if (shouldBeNoop(context, methodName)) {
+        return {};
+      }
+
+      // --------------------------------------------------------------------------
+      // Public
+      // --------------------------------------------------------------------------
+
+      return {
+        CallExpression(node) {
+          const callee = node.callee;
+          if (
+            callee.type !== 'MemberExpression'
+            || callee.object.type !== 'ThisExpression'
+            || !('name' in callee.property)
+            || callee.property.name !== 'setState'
+          ) {
+            return;
+          }
+          const ancestors = getAncestors(context, node);
+          let depth = 0;
+          findLast(ancestors, (ancestor) => {
+          // ancestors.some((ancestor) => {
+            if (/Function(Expression|Declaration)$/.test(ancestor.type)) {
+              depth += 1;
+            }
+            if (
+              (ancestor.type !== 'Property' && ancestor.type !== 'MethodDefinition' && ancestor.type !== 'ClassProperty' && ancestor.type !== 'PropertyDefinition')
+              || !nameMatches(ancestor.key.name)
+              || (mode !== 'disallow-in-func' && depth > 1)
+            ) {
+              return false;
+            }
+            report(context, messages.noSetState, 'noSetState', {
+              node: callee,
+              data: {
+                name: ancestor.key.name,
+              },
+            });
+            return true;
+          });
+        },
+      };
+    },
+  };
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+declare function _exports(messageId: any, message: any): {
+    messageId: any;
+    message?: undefined;
+} | {
+    message: any;
+    messageId?: undefined;
+};
+export = _exports;
+//# sourceMappingURL=message.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/message.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["message.js"],"names":[],"mappings":"AAKiB;;;;;;EAEhB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/message.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/message.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/message.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+'use strict';
+
+const semver = require('semver');
+const eslintPkg = require('eslint/package.json');
+
+module.exports = function getMessageData(messageId, message) {
+  return messageId && semver.satisfies(eslintPkg.version, '>= 4.15') ? { messageId } : { message };
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,16 @@
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+export function getCreateClassFromContext(context: Context): string;
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+export function getFragmentFromContext(context: Context): string;
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+export function getFromContext(context: Context): string;
+//# sourceMappingURL=pragma.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/pragma.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"pragma.d.ts","sourceRoot":"","sources":["pragma.js"],"names":[],"mappings":"AAaA;;;GAGG;AACH,mDAHW,OAAO,GACL,MAAM,CAYlB;AAED;;;GAGG;AACH,gDAHW,OAAO,GACL,MAAM,CAYlB;AAED;;;GAGG;AACH,wCAHW,OAAO,GACL,MAAM,CAqBlB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/pragma.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/pragma.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/pragma.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview Utility functions for React pragma configuration
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const getSourceCode = require('./eslint').getSourceCode;
+
+const JSX_ANNOTATION_REGEX = /@jsx\s+([^\s]+)/;
+// Does not check for reserved keywords or unicode characters
+const JS_IDENTIFIER_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
+
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+function getCreateClassFromContext(context) {
+  let pragma = 'createReactClass';
+  // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings.react && context.settings.react.createClass) {
+    pragma = context.settings.react.createClass;
+  }
+  if (!JS_IDENTIFIER_REGEX.test(pragma)) {
+    throw new Error(`createClass pragma ${pragma} is not a valid function name`);
+  }
+  return pragma;
+}
+
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+function getFragmentFromContext(context) {
+  let pragma = 'Fragment';
+  // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings.react && context.settings.react.fragment) {
+    pragma = context.settings.react.fragment;
+  }
+  if (!JS_IDENTIFIER_REGEX.test(pragma)) {
+    throw new Error(`Fragment pragma ${pragma} is not a valid identifier`);
+  }
+  return pragma;
+}
+
+/**
+ * @param {Context} context
+ * @returns {string}
+ */
+function getFromContext(context) {
+  let pragma = 'React';
+
+  const sourceCode = getSourceCode(context);
+  const pragmaNode = sourceCode.getAllComments().find((node) => JSX_ANNOTATION_REGEX.test(node.value));
+
+  if (pragmaNode) {
+    const matches = JSX_ANNOTATION_REGEX.exec(pragmaNode.value);
+    pragma = matches[1].split('.')[0];
+    // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  } else if (context.settings.react && context.settings.react.pragma) {
+    pragma = context.settings.react.pragma;
+  }
+
+  if (!JS_IDENTIFIER_REGEX.test(pragma)) {
+    console.warn(`React pragma ${pragma} is not a valid identifier`);
+    return 'React';
+  }
+  return pragma;
+}
+
+module.exports = {
+  getCreateClassFromContext,
+  getFragmentFromContext,
+  getFromContext,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,20 @@
+declare function _exports(context: any, components: any, utils: any): {
+    ClassExpression(node: any): void;
+    ClassDeclaration(node: any): void;
+    'ClassProperty, PropertyDefinition'(node: any): void;
+    ObjectExpression(node: any): void;
+    FunctionExpression(node: any): void;
+    ImportDeclaration(node: any): void;
+    FunctionDeclaration: (node: ASTNode, rootNode: ASTNode) => void;
+    ArrowFunctionExpression: (node: ASTNode, rootNode: ASTNode) => void;
+    MemberExpression(node: any): void;
+    MethodDefinition(node: any): void;
+    TypeAlias(node: any): void;
+    TypeParameterDeclaration(node: any): void;
+    Program(): void;
+    BlockStatement(): void;
+    'BlockStatement:exit'(): void;
+    'Program:exit'(): void;
+};
+export = _exports;
+//# sourceMappingURL=propTypes.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"propTypes.d.ts","sourceRoot":"","sources":["propTypes.js"],"names":[],"mappings":"AAoGiB;;;;;;;gCA06BJ,OAAO,YAEP,OAAO;oCAFP,OAAO,YAEP,OAAO;;;;;;;;;EAqQnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypes.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1302 @@
+/**
+ * @fileoverview Common propTypes detection functionality.
+ */
+
+'use strict';
+
+const flatMap = require('array.prototype.flatmap');
+
+const annotations = require('./annotations');
+const propsUtil = require('./props');
+const variableUtil = require('./variable');
+const testFlowVersion = require('./version').testFlowVersion;
+const propWrapperUtil = require('./propWrapper');
+const astUtil = require('./ast');
+const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
+const eslintUtil = require('./eslint');
+
+const getFirstTokens = eslintUtil.getFirstTokens;
+const getScope = eslintUtil.getScope;
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+/**
+ * Check if node is function type.
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isFunctionType(node) {
+  if (!node) return false;
+  const nodeType = node.type;
+  return nodeType === 'FunctionDeclaration'
+    || nodeType === 'FunctionExpression'
+    || nodeType === 'ArrowFunctionExpression';
+}
+
+/**
+ * Checks if we are declaring a props as a generic type in a flow-annotated class.
+ *
+ * @param {ASTNode} node  the AST node being checked.
+ * @returns {boolean} True if the node is a class with generic prop types, false if not.
+ */
+function isSuperTypeParameterPropsDeclaration(node) {
+  if (node && (node.type === 'ClassDeclaration' || node.type === 'ClassExpression')) {
+    const parameters = propsUtil.getSuperTypeArguments(node);
+    if (parameters && parameters.params.length > 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Iterates through a properties node, like a customized forEach.
+ * @param {Object} context Array of properties to iterate.
+ * @param {Object[]} properties Array of properties to iterate.
+ * @param {Function} fn Function to call on each property, receives property key
+    and property value. (key, value) => void
+  * @param {Function} [handleSpreadFn] Function to call on each ObjectTypeSpreadProperty, receives the
+    argument
+ */
+function iterateProperties(context, properties, fn, handleSpreadFn) {
+  if (properties && properties.length && typeof fn === 'function') {
+    for (let i = 0, j = properties.length; i < j; i++) {
+      const node = properties[i];
+      const key = astUtil.getKeyValue(context, node);
+
+      if (node.type === 'ObjectTypeSpreadProperty' && typeof handleSpreadFn === 'function') {
+        handleSpreadFn(node.argument);
+      }
+
+      const value = node.value;
+      fn(key, value, node);
+    }
+  }
+}
+
+/**
+ * Checks if a node is inside a class body.
+ *
+ * @param {ASTNode} node the AST node being checked.
+ * @returns {boolean} True if the node has a ClassBody ancestor, false if not.
+ */
+function isInsideClassBody(node) {
+  let parent = node.parent;
+  while (parent) {
+    if (parent.type === 'ClassBody') {
+      return true;
+    }
+    parent = parent.parent;
+  }
+  return false;
+}
+
+function startWithCapitalizedLetter(node) {
+  return (
+    node.parent.type === 'VariableDeclarator'
+    && !isFirstLetterCapitalized(node.parent.id.name)
+  );
+}
+
+module.exports = function propTypesInstructions(context, components, utils) {
+  // Used to track the type annotations in scope.
+  // Necessary because babel's scopes do not track type annotations.
+  let stack = null;
+
+  const classExpressions = [];
+  const defaults = { customValidators: [] };
+  const configuration = Object.assign({}, defaults, context.options[0] || {});
+  const customValidators = configuration.customValidators;
+  const allowedGenericTypes = new Set(['ComponentProps', 'ComponentPropsWithRef', 'ComponentPropsWithoutRef', 'forwardRef', 'ForwardRefRenderFunction', 'VFC', 'VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']);
+  const genericTypeParamIndexWherePropsArePresent = {
+    ComponentProps: 0,
+    ComponentPropsWithRef: 0,
+    ComponentPropsWithoutRef: 0,
+    ForwardRefRenderFunction: 1,
+    forwardRef: 1,
+    VoidFunctionComponent: 0,
+    VFC: 0,
+    PropsWithChildren: 0,
+    SFC: 0,
+    StatelessComponent: 0,
+    FunctionComponent: 0,
+    FC: 0,
+  };
+  const genericReactTypesImport = new Set();
+  // import { FC as X } from 'react' -> localToImportedMap = { x: FC }
+  const localToImportedMap = {};
+
+  /**
+   * Returns the full scope.
+   * @returns {Object} The whole scope.
+   */
+  function typeScope() {
+    return stack[stack.length - 1];
+  }
+
+  /**
+   * Gets a node from the scope.
+   * @param {string} key The name of the identifier to access.
+   * @returns {ASTNode} The ASTNode associated with the given identifier.
+   */
+  function getInTypeScope(key) {
+    return stack[stack.length - 1][key];
+  }
+
+  /**
+   * Sets the new value in the scope.
+   * @param {string} key The name of the identifier to access
+   * @param {ASTNode} value The new value for the identifier.
+   * @returns {ASTNode} The ASTNode associated with the given identifier.
+   */
+  function setInTypeScope(key, value) {
+    stack[stack.length - 1][key] = value;
+    return value;
+  }
+
+  /**
+   * Checks if prop should be validated by plugin-react-proptypes
+   * @param {string} validator Name of validator to check.
+   * @returns {boolean} True if validator should be checked by custom validator.
+   */
+  function hasCustomValidator(validator) {
+    return customValidators.indexOf(validator) !== -1;
+  }
+
+  /* eslint-disable no-use-before-define */
+  /** @type {TypeDeclarationBuilders} */
+  const typeDeclarationBuilders = {
+    GenericTypeAnnotation(annotation, parentName, seen) {
+      if (getInTypeScope(annotation.id.name)) {
+        return buildTypeAnnotationDeclarationTypes(getInTypeScope(annotation.id.name), parentName, seen);
+      }
+      return {};
+    },
+
+    ObjectTypeAnnotation(annotation, parentName, seen) {
+      let containsUnresolvedObjectTypeSpread = false;
+      let containsSpread = false;
+      const containsIndexers = !!annotation.indexers && annotation.indexers.length > 0;
+      const shapeTypeDefinition = {
+        type: 'shape',
+        children: {},
+      };
+      iterateProperties(
+        context,
+        annotation.properties,
+        (childKey, childValue, propNode) => {
+          const fullName = [parentName, childKey].join('.');
+          if (childKey || childValue) {
+            const types = buildTypeAnnotationDeclarationTypes(childValue, fullName, seen);
+            types.fullName = fullName;
+            types.name = childKey;
+            types.node = propNode;
+            types.isRequired = !childValue.optional;
+            shapeTypeDefinition.children[childKey] = types;
+          }
+        },
+        (spreadNode) => {
+          const key = astUtil.getKeyValue(context, spreadNode);
+          const types = buildTypeAnnotationDeclarationTypes(spreadNode, key, seen);
+          if (!types.children) {
+            containsUnresolvedObjectTypeSpread = true;
+          } else {
+            Object.assign(shapeTypeDefinition, types.children);
+          }
+          containsSpread = true;
+        }
+      );
+
+      // Mark if this shape has spread or an indexer. We will know to consider all props from this shape as having propTypes,
+      // but still have the ability to detect unused children of this shape.
+      shapeTypeDefinition.containsUnresolvedSpread = containsUnresolvedObjectTypeSpread;
+      shapeTypeDefinition.containsIndexers = containsIndexers;
+      // Deprecated: containsSpread is not used anymore in the codebase, ensure to keep API backward compatibility
+      shapeTypeDefinition.containsSpread = containsSpread;
+
+      return shapeTypeDefinition;
+    },
+
+    UnionTypeAnnotation(annotation, parentName, seen) {
+      /** @type {UnionTypeDefinition} */
+      const unionTypeDefinition = {
+        type: 'union',
+        children: annotation.types.map((type) => buildTypeAnnotationDeclarationTypes(type, parentName, seen)),
+      };
+      if (unionTypeDefinition.children.length === 0) {
+        // no complex type found, simply accept everything
+        return {};
+      }
+      return unionTypeDefinition;
+    },
+
+    ArrayTypeAnnotation(annotation, parentName, seen) {
+      const fullName = [parentName, '*'].join('.');
+      const child = buildTypeAnnotationDeclarationTypes(annotation.elementType, fullName, seen);
+      child.fullName = fullName;
+      child.name = '__ANY_KEY__';
+      child.node = annotation;
+      return {
+        type: 'object',
+        children: {
+          __ANY_KEY__: child,
+        },
+      };
+    },
+  };
+  /* eslint-enable no-use-before-define */
+
+  /**
+   * Resolve the type annotation for a given node.
+   * Flow annotations are sometimes wrapped in outer `TypeAnnotation`
+   * and `NullableTypeAnnotation` nodes which obscure the annotation we're
+   * interested in.
+   * This method also resolves type aliases where possible.
+   *
+   * @param {ASTNode} node The annotation or a node containing the type annotation.
+   * @returns {ASTNode} The resolved type annotation for the node.
+   */
+  function resolveTypeAnnotation(node) {
+    let annotation = (node.left && node.left.typeAnnotation) || node.typeAnnotation || node;
+    while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
+      annotation = annotation.typeAnnotation;
+    }
+    if (annotation.type === 'GenericTypeAnnotation' && getInTypeScope(annotation.id.name)) {
+      return getInTypeScope(annotation.id.name);
+    }
+    return annotation;
+  }
+
+  /**
+   * Creates the representation of the React props type annotation for the component.
+   * The representation is used to verify nested used properties.
+   * @param {ASTNode} annotation Type annotation for the props class property.
+   * @param {string} parentName
+   * @param {Set<ASTNode>} [seen]
+   * @return {Object} The representation of the declaration, empty object means
+   *    the property is declared without the need for further analysis.
+   */
+  function buildTypeAnnotationDeclarationTypes(annotation, parentName, seen) {
+    if (typeof seen === 'undefined') {
+      // Keeps track of annotations we've already seen to
+      // prevent problems with recursive types.
+      seen = new Set();
+    }
+    if (seen.has(annotation)) {
+      // This must be a recursive type annotation, so just accept anything.
+      return {};
+    }
+    seen.add(annotation);
+
+    if (annotation.type in typeDeclarationBuilders) {
+      return typeDeclarationBuilders[annotation.type](annotation, parentName, seen);
+    }
+    return {};
+  }
+
+  /**
+   * Marks all props found inside ObjectTypeAnnotation as declared.
+   *
+   * Modifies the declaredProperties object
+   * @param {ASTNode} propTypes
+   * @param {Object} declaredPropTypes
+   * @returns {boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
+   */
+  function declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes) {
+    let ignorePropsValidation = false;
+
+    iterateProperties(context, propTypes.properties, (key, value, propNode) => {
+      if (!value) {
+        ignorePropsValidation = ignorePropsValidation || propNode.type !== 'ObjectTypeSpreadProperty';
+        return;
+      }
+
+      const types = buildTypeAnnotationDeclarationTypes(value, key);
+      types.fullName = key;
+      types.name = key;
+      types.node = propNode;
+      types.isRequired = !propNode.optional;
+      declaredPropTypes[key] = types;
+    }, (spreadNode) => {
+      const key = astUtil.getKeyValue(context, spreadNode);
+      const spreadAnnotation = getInTypeScope(key);
+      if (!spreadAnnotation) {
+        ignorePropsValidation = true;
+      } else {
+        const spreadIgnoreValidation = declarePropTypesForObjectTypeAnnotation(spreadAnnotation, declaredPropTypes);
+        ignorePropsValidation = ignorePropsValidation || spreadIgnoreValidation;
+      }
+    });
+
+    return ignorePropsValidation;
+  }
+
+  /**
+   * Marks all props found inside IntersectionTypeAnnotation as declared.
+   * Since InterSectionTypeAnnotations can be nested, this handles recursively.
+   *
+   * Modifies the declaredPropTypes object
+   * @param {ASTNode} propTypes
+   * @param {Object} declaredPropTypes
+   * @returns {boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
+   */
+  function declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes) {
+    return propTypes.types.some((annotation) => {
+      if (annotation.type === 'ObjectTypeAnnotation') {
+        return declarePropTypesForObjectTypeAnnotation(annotation, declaredPropTypes);
+      }
+
+      if (annotation.type === 'UnionTypeAnnotation') {
+        return true;
+      }
+
+      // Type can't be resolved
+      if (!annotation.id) {
+        return true;
+      }
+
+      const typeNode = getInTypeScope(annotation.id.name);
+
+      if (!typeNode) {
+        return true;
+      }
+      if (typeNode.type === 'IntersectionTypeAnnotation') {
+        return declarePropTypesForIntersectionTypeAnnotation(typeNode, declaredPropTypes);
+      }
+
+      return declarePropTypesForObjectTypeAnnotation(typeNode, declaredPropTypes);
+    });
+  }
+
+  /**
+   * Resolve node of type Identifier when building declaration types.
+   * @param {ASTNode} node
+   * @param {ASTNode} rootNode
+   * @param {Function} callback called with the resolved value only if resolved.
+   */
+  function resolveValueForIdentifierNode(node, rootNode, callback) {
+    if (
+      rootNode
+      && node
+      && node.type === 'Identifier'
+    ) {
+      const scope = getScope(context, rootNode);
+      const identVariable = scope.variableScope.variables.find(
+        (variable) => variable.name === node.name
+      );
+      if (identVariable) {
+        const definition = identVariable.defs[identVariable.defs.length - 1];
+        callback(definition.node.init);
+      }
+    }
+  }
+
+  /**
+   * Creates the representation of the React propTypes for the component.
+   * The representation is used to verify nested used properties.
+   * @param {ASTNode} value Node of the PropTypes for the desired property
+   * @param {string} parentName
+   * @param {ASTNode} rootNode
+   * @return {Object} The representation of the declaration, empty object means
+   *    the property is declared without the need for further analysis.
+   */
+  function buildReactDeclarationTypes(value, parentName, rootNode) {
+    if (
+      value
+      && value.callee
+      && value.callee.object
+      && hasCustomValidator(value.callee.object.name)
+    ) {
+      return {};
+    }
+
+    let identNodeResolved = false;
+    // Resolve identifier node for cases where isRequired is set in
+    // the variable declaration or not at all.
+    // const variableType = PropTypes.shape({ foo: ... }).isRequired
+    // propTypes = {
+    //   example: variableType
+    // }
+    // --------
+    // const variableType = PropTypes.shape({ foo: ... })
+    // propTypes = {
+    //   example: variableType
+    // }
+    resolveValueForIdentifierNode(value, rootNode, (newValue) => {
+      identNodeResolved = true;
+      value = newValue;
+    });
+
+    if (
+      value
+      && value.type === 'MemberExpression'
+      && value.property
+      && value.property.name === 'isRequired'
+    ) {
+      value = value.object;
+    }
+
+    // Resolve identifier node for cases where isRequired is set in
+    // the prop types.
+    // const variableType = PropTypes.shape({ foo: ... })
+    // propTypes = {
+    //   example: variableType.isRequired
+    // }
+    if (!identNodeResolved) {
+      resolveValueForIdentifierNode(value, rootNode, (newValue) => {
+        value = newValue;
+      });
+    }
+
+    // Verify PropTypes that are functions
+    if (
+      astUtil.isCallExpression(value)
+      && value.callee
+      && value.callee.property
+      && value.callee.property.name
+      && value.arguments
+      && value.arguments.length > 0
+    ) {
+      const callName = value.callee.property.name;
+      const argument = value.arguments[0];
+      switch (callName) {
+        case 'shape':
+        case 'exact': {
+          if (argument.type !== 'ObjectExpression') {
+            // Invalid proptype or cannot analyse statically
+            return {};
+          }
+          const shapeTypeDefinition = {
+            type: callName,
+            children: {},
+          };
+          iterateProperties(context, argument.properties, (childKey, childValue, propNode) => {
+            if (childValue) { // skip spread propTypes
+              const fullName = [parentName, childKey].join('.');
+              const types = buildReactDeclarationTypes(childValue, fullName, rootNode);
+              types.fullName = fullName;
+              types.name = childKey;
+              types.node = propNode;
+              shapeTypeDefinition.children[childKey] = types;
+            }
+          });
+          return shapeTypeDefinition;
+        }
+        case 'arrayOf':
+        case 'objectOf': {
+          const fullName = [parentName, '*'].join('.');
+          const child = buildReactDeclarationTypes(argument, fullName, rootNode);
+          child.fullName = fullName;
+          child.name = '__ANY_KEY__';
+          child.node = argument;
+          return {
+            type: 'object',
+            children: {
+              __ANY_KEY__: child,
+            },
+          };
+        }
+        case 'oneOfType': {
+          if (
+            !argument.elements
+            || argument.elements.length === 0
+          ) {
+            // Invalid proptype or cannot analyse statically
+            return {};
+          }
+
+          /** @type {UnionTypeDefinition} */
+          const unionTypeDefinition = {
+            type: 'union',
+            children: argument.elements.map((element) => buildReactDeclarationTypes(element, parentName, rootNode)),
+          };
+          if (unionTypeDefinition.children.length === 0) {
+            // no complex type found, simply accept everything
+            return {};
+          }
+          return unionTypeDefinition;
+        }
+        default:
+          return {};
+      }
+    }
+    // Unknown property or accepts everything (any, object, ...)
+    return {};
+  }
+
+  function isValidReactGenericTypeAnnotation(annotation) {
+    if (annotation.typeName) {
+      if (annotation.typeName.name) { // if FC<Props>
+        const typeName = annotation.typeName.name;
+        if (!genericReactTypesImport.has(typeName)) {
+          return false;
+        }
+      } else if (annotation.typeName.right.name) { // if React.FC<Props>
+        const right = annotation.typeName.right.name;
+        const left = annotation.typeName.left.name;
+
+        if (!genericReactTypesImport.has(left) || !allowedGenericTypes.has(right)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the left most typeName of a node, e.g: FC<Props>, React.FC<Props>
+   * The representation is used to verify nested used properties.
+   * @param {ASTNode} node
+   * @return {string | undefined}
+   */
+  function getLeftMostTypeName(node) {
+    if (node.name) return node.name;
+    if (node.left) return getLeftMostTypeName(node.left);
+  }
+
+  function getRightMostTypeName(node) {
+    if (node.name) return node.name;
+    if (node.right) return getRightMostTypeName(node.right);
+  }
+
+  /**
+   * Returns true if the node is either a interface or type alias declaration
+   * @param {ASTNode} node
+   * @return {boolean}
+   */
+  function filterInterfaceOrTypeAlias(node) {
+    return (
+      astUtil.isTSInterfaceDeclaration(node) || astUtil.isTSTypeAliasDeclaration(node)
+    );
+  }
+
+  /**
+   * Returns true if the interface or type alias declaration node name matches the type-name str
+   * @param {ASTNode} node
+   * @param {string} typeName
+   * @return {boolean}
+   */
+  function filterInterfaceOrAliasByName(node, typeName) {
+    return (
+      node.id
+      && node.id.name === typeName
+    ) || (
+      node.declaration
+      && node.declaration.id
+      && node.declaration.id.name === typeName
+    );
+  }
+
+  class DeclarePropTypesForTSTypeAnnotation {
+    constructor(propTypes, declaredPropTypes, rootNode) {
+      this.propTypes = propTypes;
+      this.declaredPropTypes = declaredPropTypes;
+      this.foundDeclaredPropertiesList = [];
+      this.referenceNameMap = new Set();
+      this.sourceCode = getSourceCode(context);
+      this.shouldIgnorePropTypes = false;
+      this.rootNode = rootNode;
+      this.visitTSNode(this.propTypes);
+      this.endAndStructDeclaredPropTypes();
+    }
+
+    /**
+     * The node will be distribute to different function.
+     * @param {ASTNode} node
+     */
+    visitTSNode(node) {
+      if (!node) return;
+      if (astUtil.isTSTypeAnnotation(node)) {
+        const typeAnnotation = node.typeAnnotation;
+        this.visitTSNode(typeAnnotation);
+      } else if (astUtil.isTSTypeReference(node)) {
+        this.searchDeclarationByName(node);
+      } else if (astUtil.isTSInterfaceHeritage(node)) {
+        this.searchDeclarationByName(node);
+      } else if (astUtil.isTSTypeLiteral(node)) {
+        // Check node is an object literal
+        if (Array.isArray(node.members)) {
+          this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(node.members);
+        }
+      } else if (astUtil.isTSIntersectionType(node)) {
+        this.convertIntersectionTypeToPropTypes(node);
+      } else if (astUtil.isTSParenthesizedType(node)) {
+        const typeAnnotation = node.typeAnnotation;
+        this.visitTSNode(typeAnnotation);
+      } else if (astUtil.isTSTypeParameterInstantiation(node)) {
+        if (Array.isArray(node.params)) {
+          node.params.forEach((x) => this.visitTSNode(x));
+        }
+      } else {
+        this.shouldIgnorePropTypes = true;
+      }
+    }
+
+    /**
+     * Search TSInterfaceDeclaration or TSTypeAliasDeclaration,
+     * by using TSTypeReference and TSInterfaceHeritage name.
+     * @param {ASTNode} node
+     */
+    searchDeclarationByName(node) {
+      let typeName;
+      if (astUtil.isTSTypeReference(node)) {
+        typeName = node.typeName.name;
+        const leftMostName = getLeftMostTypeName(node.typeName);
+        const shouldTraverseTypeParams = genericReactTypesImport.has(leftMostName);
+        const nodeTypeArguments = propsUtil.getTypeArguments(node);
+        if (shouldTraverseTypeParams && nodeTypeArguments && nodeTypeArguments.length !== 0) {
+          // All react Generic types are derived from:
+          // type PropsWithChildren<P> = P & { children?: ReactNode | undefined }
+          // So we should construct an optional children prop
+          this.shouldSpecifyOptionalChildrenProps = true;
+
+          const rightMostName = getRightMostTypeName(node.typeName);
+          if (
+            leftMostName === 'React'
+            && (
+              rightMostName === 'HTMLAttributes'
+              || rightMostName === 'HTMLElement'
+              || rightMostName === 'HTMLProps'
+            )
+          ) {
+            this.shouldSpecifyClassNameProp = true;
+          }
+
+          const importedName = localToImportedMap[rightMostName];
+          const idx = genericTypeParamIndexWherePropsArePresent[
+            leftMostName !== rightMostName ? rightMostName : importedName
+          ];
+          const nextNode = nodeTypeArguments.params[idx];
+          this.visitTSNode(nextNode);
+          return;
+        }
+      } else if (astUtil.isTSInterfaceHeritage(node)) {
+        if (!node.expression && node.id) {
+          typeName = node.id.name;
+        } else {
+          typeName = node.expression.name;
+        }
+      }
+      if (!typeName) {
+        this.shouldIgnorePropTypes = true;
+        return;
+      }
+      if (typeName === 'ReturnType') {
+        this.convertReturnTypeToPropTypes(node, this.rootNode);
+        return;
+      }
+      // Prevent recursive inheritance will cause maximum callstack.
+      if (this.referenceNameMap.has(typeName)) {
+        this.shouldIgnorePropTypes = true;
+        return;
+      }
+      // Add typeName to Set and consider it as traversed.
+      this.referenceNameMap.add(typeName);
+
+      /**
+       * From line 577 to line 581, and line 588 to line 590 are trying to handle typescript-eslint-parser
+       * Need to be deprecated after remove typescript-eslint-parser support.
+       */
+      const candidateTypes = this.sourceCode.ast.body.filter((item) => astUtil.isTSTypeDeclaration(item));
+
+      const declarations = flatMap(
+        candidateTypes,
+        (type) => (
+          type.declarations
+          || (
+            type.declaration
+            && type.declaration.declarations
+          )
+          || type.declaration
+        )
+      );
+
+      // we tried to find either an interface or a type with the TypeReference name
+      const typeDeclaration = declarations.filter((dec) => dec.id.name === typeName);
+
+      const interfaceDeclarations = this.sourceCode.ast.body
+        .filter(filterInterfaceOrTypeAlias)
+        .filter((item) => filterInterfaceOrAliasByName(item, typeName))
+        .map((item) => (item.declaration || item));
+
+      if (typeDeclaration.length !== 0) {
+        typeDeclaration.map((t) => t.init || t.typeAnnotation).forEach(this.visitTSNode, this);
+      } else if (interfaceDeclarations.length !== 0) {
+        interfaceDeclarations.forEach(this.traverseDeclaredInterfaceOrTypeAlias, this);
+      } else {
+        this.shouldIgnorePropTypes = true;
+      }
+    }
+
+    /**
+     * Traverse TSInterfaceDeclaration and TSTypeAliasDeclaration
+     * which retrieve from function searchDeclarationByName;
+     * @param {ASTNode} node
+     */
+    traverseDeclaredInterfaceOrTypeAlias(node) {
+      if (astUtil.isTSInterfaceDeclaration(node)) {
+        // Handle TSInterfaceDeclaration interface Props { name: string, id: number}, should put in properties list directly;
+        this.foundDeclaredPropertiesList = this.foundDeclaredPropertiesList.concat(node.body.body);
+      }
+      // Handle TSTypeAliasDeclaration type Props = {name:string}
+      if (astUtil.isTSTypeAliasDeclaration(node)) {
+        const typeAnnotation = node.typeAnnotation;
+        this.visitTSNode(typeAnnotation);
+      }
+      if (Array.isArray(node.extends)) {
+        node.extends.forEach((x) => this.visitTSNode(x));
+        // This line is trying to handle typescript-eslint-parser
+        // typescript-eslint-parser extension is name as heritage
+      } else if (Array.isArray(node.heritage)) {
+        node.heritage.forEach((x) => this.visitTSNode(x));
+      }
+    }
+
+    convertIntersectionTypeToPropTypes(node) {
+      if (!node) return;
+      if (Array.isArray(node.types)) {
+        node.types.forEach((x) => this.visitTSNode(x));
+      } else {
+        this.shouldIgnorePropTypes = true;
+      }
+    }
+
+    convertReturnTypeToPropTypes(node, rootNode) {
+      // ReturnType<T> should always have one parameter
+      const nodeTypeArguments = propsUtil.getTypeArguments(node);
+      if (nodeTypeArguments) {
+        if (nodeTypeArguments.params.length === 1) {
+          let returnType = nodeTypeArguments.params[0];
+          // This line is trying to handle typescript-eslint-parser
+          // typescript-eslint-parser TSTypeQuery is wrapped by TSTypeReference
+          if (astUtil.isTSTypeReference(returnType)) {
+            returnType = returnType.typeName;
+          }
+          // Handle ReturnType<typeof mapStateToProps>
+          if (astUtil.isTSTypeQuery(returnType)) {
+            const returnTypeFunction = flatMap(this.sourceCode.ast.body
+              .filter((item) => item.type === 'VariableDeclaration'
+                && item.declarations.find((dec) => dec.id.name === returnType.exprName.name)
+              ), (type) => type.declarations).map((dec) => dec.init);
+
+            if (Array.isArray(returnTypeFunction)) {
+              if (returnTypeFunction.length === 0) {
+                // Cannot find identifier in current scope. It might be an exported type.
+                this.shouldIgnorePropTypes = true;
+                return;
+              }
+              returnTypeFunction.forEach((func) => {
+                if (isFunctionType(func)) {
+                  let res = func.body;
+                  if (res.type === 'BlockStatement') {
+                    res = astUtil.findReturnStatement(func);
+                    if (res) {
+                      res = res.argument;
+                    }
+                  }
+                  switch (res.type) {
+                    case 'ObjectExpression':
+                      iterateProperties(context, res.properties, (key, value, propNode) => {
+                        if (propNode && astUtil.isCallExpression(propNode.argument)) {
+                          const propNodeTypeArguments = propsUtil.getTypeArguments(propNode.argument);
+                          if (propNodeTypeArguments) {
+                            this.visitTSNode(propNodeTypeArguments);
+                          } else {
+                            // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
+                            this.shouldIgnorePropTypes = true;
+                            return;
+                          }
+                        }
+                        if (!value) {
+                          this.shouldIgnorePropTypes = true;
+                          return;
+                        }
+                        const types = buildReactDeclarationTypes(value, key, rootNode);
+                        types.fullName = key;
+                        types.name = key;
+                        types.node = propNode;
+                        types.isRequired = propsUtil.isRequiredPropType(value);
+                        this.declaredPropTypes[key] = types;
+                      });
+                      break;
+                    case 'CallExpression':
+                      if (propsUtil.getTypeArguments(res)) {
+                        this.visitTSNode(propsUtil.getTypeArguments(res));
+                      } else {
+                        // Ignore this CallExpression return value since it doesn't have any typeParameters to let us know it's types.
+                        this.shouldIgnorePropTypes = true;
+                      }
+                      break;
+                    default:
+                  }
+                }
+              });
+              return;
+            }
+          }
+          // Handle ReturnType<()=>returnType>
+          if (astUtil.isTSFunctionType(returnType)) {
+            if (astUtil.isTSTypeAnnotation(returnType.returnType)) {
+              this.visitTSNode(returnType.returnType);
+              return;
+            }
+            // This line is trying to handle typescript-eslint-parser
+            // typescript-eslint-parser TSFunction name returnType as typeAnnotation
+            if (astUtil.isTSTypeAnnotation(returnType.typeAnnotation)) {
+              this.visitTSNode(returnType.typeAnnotation);
+              return;
+            }
+          }
+        }
+      }
+      this.shouldIgnorePropTypes = true;
+    }
+
+    endAndStructDeclaredPropTypes() {
+      if (this.shouldSpecifyOptionalChildrenProps) {
+        this.declaredPropTypes.children = {
+          fullName: 'children',
+          name: 'children',
+          isRequired: false,
+        };
+      }
+      if (this.shouldSpecifyClassNameProp) {
+        this.declaredPropTypes.className = {
+          fullName: 'className',
+          name: 'className',
+          isRequired: false,
+        };
+      }
+
+      this.foundDeclaredPropertiesList.forEach((tsInterfaceBody) => {
+        if (tsInterfaceBody && (tsInterfaceBody.type === 'TSPropertySignature' || tsInterfaceBody.type === 'TSMethodSignature')) {
+          let accessor = 'name';
+          if (tsInterfaceBody.key.type === 'Literal') {
+            if (typeof tsInterfaceBody.key.value === 'number') {
+              accessor = 'raw';
+            } else {
+              accessor = 'value';
+            }
+          }
+          this.declaredPropTypes[tsInterfaceBody.key[accessor]] = {
+            fullName: tsInterfaceBody.key[accessor],
+            name: tsInterfaceBody.key[accessor],
+            node: tsInterfaceBody,
+            isRequired: !tsInterfaceBody.optional,
+          };
+        }
+      });
+    }
+  }
+
+  /**
+   * Mark a prop type as declared
+   * @param {ASTNode} node The AST node being checked.
+   * @param {ASTNode} propTypes The AST node containing the proptypes
+   * @param {ASTNode} rootNode
+   */
+  function markPropTypesAsDeclared(node, propTypes, rootNode) {
+    let componentNode = node;
+    while (componentNode && !components.get(componentNode)) {
+      componentNode = componentNode.parent;
+    }
+    const component = components.get(componentNode);
+    let declaredPropTypes = (component && component.declaredPropTypes) || {};
+    let ignorePropsValidation = (component && component.ignorePropsValidation) || false;
+    switch (propTypes && propTypes.type) {
+      case 'ObjectTypeAnnotation':
+        ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes);
+        break;
+      case 'ObjectExpression':
+        iterateProperties(context, propTypes.properties, (key, value, propNode) => {
+          if (!value) {
+            ignorePropsValidation = true;
+            return;
+          }
+          const types = buildReactDeclarationTypes(value, key, rootNode);
+          types.fullName = key;
+          types.name = key;
+          types.node = propNode;
+          types.isRequired = propsUtil.isRequiredPropType(value);
+          declaredPropTypes[key] = types;
+        });
+        break;
+      case 'MemberExpression': {
+        let curDeclaredPropTypes = declaredPropTypes;
+        // Walk the list of properties, until we reach the assignment
+        // ie: ClassX.propTypes.a.b.c = ...
+        while (
+          propTypes
+          && propTypes.parent
+          && propTypes.parent.type !== 'AssignmentExpression'
+          && propTypes.property
+          && curDeclaredPropTypes
+        ) {
+          const propName = propTypes.property.name;
+          if (propName in curDeclaredPropTypes) {
+            curDeclaredPropTypes = curDeclaredPropTypes[propName].children;
+            propTypes = propTypes.parent;
+          } else {
+            // This will crash at runtime because we haven't seen this key before
+            // stop this and do not declare it
+            propTypes = null;
+          }
+        }
+        if (propTypes && propTypes.parent && propTypes.property) {
+          if (!(propTypes === propTypes.parent.left && propTypes.parent.left.object)) {
+            ignorePropsValidation = true;
+            break;
+          }
+          const parentProp = getText(context, propTypes.parent.left.object).replace(/^.*\.propTypes\./, '');
+          const types = buildReactDeclarationTypes(
+            propTypes.parent.right,
+            parentProp,
+            rootNode
+          );
+
+          types.name = propTypes.property.name;
+          types.fullName = [parentProp, propTypes.property.name].join('.');
+          types.node = propTypes.parent;
+          types.isRequired = propsUtil.isRequiredPropType(propTypes.parent.right);
+          curDeclaredPropTypes[propTypes.property.name] = types;
+        } else {
+          let isUsedInPropTypes = false;
+          let n = propTypes;
+          while (n) {
+            if (((n.type === 'AssignmentExpression') && propsUtil.isPropTypesDeclaration(n.left))
+              || ((n.type === 'ClassProperty' || n.type === 'PropertyDefinition' || n.type === 'Property') && propsUtil.isPropTypesDeclaration(n))) {
+              // Found a propType used inside of another propType. This is not considered usage, we'll still validate
+              // this component.
+              isUsedInPropTypes = true;
+              break;
+            }
+            n = n.parent;
+          }
+          if (!isUsedInPropTypes) {
+            ignorePropsValidation = true;
+          }
+        }
+        break;
+      }
+      case 'Identifier': {
+        const firstMatchingVariable = variableUtil.getVariableFromContext(context, node, propTypes.name);
+        if (firstMatchingVariable) {
+          const defInScope = firstMatchingVariable.defs[firstMatchingVariable.defs.length - 1];
+          markPropTypesAsDeclared(node, defInScope.node && defInScope.node.init, rootNode);
+          return;
+        }
+        ignorePropsValidation = true;
+        break;
+      }
+      case 'CallExpression': {
+        if (
+          propWrapperUtil.isPropWrapperFunction(
+            context,
+            getText(context, propTypes.callee)
+          )
+          && propTypes.arguments && propTypes.arguments[0]
+        ) {
+          markPropTypesAsDeclared(node, propTypes.arguments[0], rootNode);
+          return;
+        }
+        break;
+      }
+      case 'IntersectionTypeAnnotation':
+        ignorePropsValidation = declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes);
+        break;
+      case 'GenericTypeAnnotation':
+        if (propTypes.id.name === '$ReadOnly') {
+          const propTypeArguments = propsUtil.getTypeArguments(propTypes);
+          ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(
+            propTypeArguments.params[0],
+            declaredPropTypes
+          );
+        } else {
+          ignorePropsValidation = true;
+        }
+        break;
+      case 'TSTypeReference':
+      case 'TSTypeAnnotation': {
+        const tsTypeAnnotation = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes, rootNode);
+        ignorePropsValidation = tsTypeAnnotation.shouldIgnorePropTypes;
+        declaredPropTypes = tsTypeAnnotation.declaredPropTypes;
+      }
+        break;
+      case null:
+        break;
+      default:
+        ignorePropsValidation = true;
+        break;
+    }
+
+    components.set(node, {
+      declaredPropTypes,
+      ignorePropsValidation,
+    });
+  }
+
+  /**
+   * @param {ASTNode} node We expect either an ArrowFunctionExpression,
+   *   FunctionDeclaration, or FunctionExpression
+   * @param {ASTNode} rootNode
+   */
+  function markAnnotatedFunctionArgumentsAsDeclared(node, rootNode) {
+    if (!node.params || !node.params.length) {
+      return;
+    }
+
+    let propTypesArguments = null;
+    if (node.parent) {
+      propTypesArguments = propsUtil.getTypeArguments(node.parent);
+    }
+
+    if (
+      node.parent
+      && node.parent.callee
+      && propTypesArguments
+      && propTypesArguments.params
+      && (
+        node.parent.callee.name === 'forwardRef' || (
+          node.parent.callee.object
+          && node.parent.callee.property
+          && node.parent.callee.object.name === 'React'
+          && node.parent.callee.property.name === 'forwardRef'
+        )
+      )
+    ) {
+      const declaredPropTypes = {};
+      const obj = new DeclarePropTypesForTSTypeAnnotation(propTypesArguments.params[1], declaredPropTypes, rootNode);
+      components.set(node, {
+        declaredPropTypes: obj.declaredPropTypes,
+        ignorePropsValidation: obj.shouldIgnorePropTypes,
+      });
+      return;
+    }
+
+    const siblingIdentifier = node.parent && node.parent.id;
+    const siblingHasTypeAnnotation = siblingIdentifier && siblingIdentifier.typeAnnotation;
+    const isNodeAnnotated = annotations.isAnnotatedFunctionPropsDeclaration(node, context);
+
+    if (!isNodeAnnotated && !siblingHasTypeAnnotation) {
+      return;
+    }
+
+    // https://github.com/jsx-eslint/eslint-plugin-react/issues/2784
+    if (isInsideClassBody(node) && !astUtil.isFunction(node)) {
+      return;
+    }
+
+    // Should ignore function that not return JSXElement
+    if (!utils.isReturningJSXOrNull(node) || startWithCapitalizedLetter(node)) {
+      return;
+    }
+
+    if (isNodeAnnotated) {
+      const param = node.params[0];
+      if (param.typeAnnotation && param.typeAnnotation.typeAnnotation && param.typeAnnotation.typeAnnotation.type === 'UnionTypeAnnotation') {
+        param.typeAnnotation.typeAnnotation.types.forEach((annotation) => {
+          if (annotation.type === 'GenericTypeAnnotation') {
+            markPropTypesAsDeclared(node, resolveTypeAnnotation(annotation), rootNode);
+          } else {
+            markPropTypesAsDeclared(node, annotation, rootNode);
+          }
+        });
+      } else {
+        markPropTypesAsDeclared(node, resolveTypeAnnotation(param), rootNode);
+      }
+    } else {
+      // implements what's discussed here: https://github.com/jsx-eslint/eslint-plugin-react/issues/2777#issuecomment-683944481
+      const annotation = siblingIdentifier.typeAnnotation.typeAnnotation;
+
+      if (
+        annotation
+        && annotation.type !== 'TSTypeReference'
+        && propsUtil.getTypeArguments(annotation) == null
+      ) {
+        return;
+      }
+
+      if (!isValidReactGenericTypeAnnotation(annotation)) return;
+
+      markPropTypesAsDeclared(node, resolveTypeAnnotation(siblingIdentifier), rootNode);
+    }
+  }
+
+  /**
+   * Resolve the type annotation for a given class declaration node.
+   *
+   * @param {ASTNode} node The annotation or a node containing the type annotation.
+   * @returns {ASTNode} The resolved type annotation for the node.
+   */
+  function resolveSuperParameterPropsType(node) {
+    let propsParameterPosition;
+    const parameters = propsUtil.getSuperTypeArguments(node);
+
+    try {
+      // Flow <=0.52 had 3 required TypedParameters of which the second one is the Props.
+      // Flow >=0.53 has 2 optional TypedParameters of which the first one is the Props.
+      propsParameterPosition = testFlowVersion(context, '>= 0.53.0') ? 0 : 1;
+    } catch (e) {
+      // In case there is no flow version defined, we can safely assume that when there are 3 Props we are dealing with version <= 0.52
+      propsParameterPosition = parameters.params.length <= 2 ? 0 : 1;
+    }
+
+    let annotation = parameters.params[propsParameterPosition];
+    while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
+      annotation = annotation.typeAnnotation;
+    }
+
+    if (annotation && annotation.type === 'GenericTypeAnnotation' && getInTypeScope(annotation.id.name)) {
+      return getInTypeScope(annotation.id.name);
+    }
+    return annotation;
+  }
+
+  /**
+   * Checks if we are declaring a `props` class property with a flow type annotation.
+   * @param {ASTNode} node The AST node being checked.
+   * @returns {boolean} True if the node is a type annotated props declaration, false if not.
+   */
+  function isAnnotatedClassPropsDeclaration(node) {
+    if (node && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')) {
+      const tokens = getFirstTokens(context, node, 2);
+      if (
+        node.typeAnnotation && (
+          tokens[0].value === 'props'
+          || (tokens[1] && tokens[1].value === 'props')
+        )
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  return {
+    ClassExpression(node) {
+      // TypeParameterDeclaration need to be added to typeScope in order to handle ClassExpressions.
+      // This visitor is executed before TypeParameterDeclaration are scoped, therefore we postpone
+      // processing class expressions until when the program exists.
+      classExpressions.push(node);
+    },
+
+    ClassDeclaration(node) {
+      if (isSuperTypeParameterPropsDeclaration(node)) {
+        markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node), node);
+      }
+    },
+
+    'ClassProperty, PropertyDefinition'(node) {
+      if (isAnnotatedClassPropsDeclaration(node)) {
+        markPropTypesAsDeclared(node, resolveTypeAnnotation(node), node);
+      } else if (propsUtil.isPropTypesDeclaration(node)) {
+        markPropTypesAsDeclared(node, node.value, node);
+      }
+    },
+
+    ObjectExpression(node) {
+      // Search for the proptypes declaration
+      node.properties.forEach((property) => {
+        if (!propsUtil.isPropTypesDeclaration(property)) {
+          return;
+        }
+        markPropTypesAsDeclared(node, property.value, node);
+      });
+    },
+
+    FunctionExpression(node) {
+      if (node.parent.type !== 'MethodDefinition') {
+        markAnnotatedFunctionArgumentsAsDeclared(node, node);
+      }
+    },
+
+    ImportDeclaration(node) {
+      // parse `import ... from 'react`
+      if (node.source.value === 'react') {
+        node.specifiers.forEach((specifier) => {
+          if (
+            // handles import * as X from 'react'
+            specifier.type === 'ImportNamespaceSpecifier'
+            // handles import React from 'react'
+            || specifier.type === 'ImportDefaultSpecifier'
+          ) {
+            genericReactTypesImport.add(specifier.local.name);
+          }
+
+          // handles import { FC } from 'react' or import { FC as X } from 'react'
+          if (specifier.type === 'ImportSpecifier' && allowedGenericTypes.has(specifier.imported.name)) {
+            genericReactTypesImport.add(specifier.local.name);
+            localToImportedMap[specifier.local.name] = specifier.imported.name;
+          }
+        });
+      }
+    },
+
+    FunctionDeclaration: markAnnotatedFunctionArgumentsAsDeclared,
+
+    ArrowFunctionExpression: markAnnotatedFunctionArgumentsAsDeclared,
+
+    MemberExpression(node) {
+      if (propsUtil.isPropTypesDeclaration(node)) {
+        const component = utils.getRelatedComponent(node);
+        if (!component) {
+          return;
+        }
+        try {
+          markPropTypesAsDeclared(component.node, node.parent.right || node.parent, node);
+        } catch (e) {
+          if (e.constructor !== RangeError) { throw e; }
+        }
+      }
+    },
+
+    MethodDefinition(node) {
+      if (!node.static || node.kind !== 'get' || !propsUtil.isPropTypesDeclaration(node)) {
+        return;
+      }
+
+      let i = node.value.body.body.length - 1;
+      for (; i >= 0; i--) {
+        if (node.value.body.body[i].type === 'ReturnStatement') {
+          break;
+        }
+      }
+
+      if (i >= 0) {
+        markPropTypesAsDeclared(node, node.value.body.body[i].argument, node);
+      }
+    },
+
+    TypeAlias(node) {
+      setInTypeScope(node.id.name, node.right);
+    },
+
+    TypeParameterDeclaration(node) {
+      const identifier = node.params[0];
+
+      if (identifier.typeAnnotation) {
+        setInTypeScope(identifier.name, identifier.typeAnnotation.typeAnnotation);
+      }
+    },
+
+    Program() {
+      stack = [{}];
+    },
+
+    BlockStatement() {
+      stack.push(Object.create(typeScope()));
+    },
+
+    'BlockStatement:exit'() {
+      stack.pop();
+    },
+
+    'Program:exit'() {
+      classExpressions.forEach((node) => {
+        if (isSuperTypeParameterPropsDeclaration(node)) {
+          markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node), node);
+        }
+      });
+    },
+  };
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,40 @@
+/**
+ * Fixes sort order of prop types.
+ *
+ * @param {Context} context the second element to compare.
+ * @param {Fixer} fixer the first element to compare.
+ * @param {Array} declarations The context of the two nodes.
+ * @param {boolean=} ignoreCase whether or not to ignore case when comparing the two elements.
+ * @param {boolean=} requiredFirst whether or not to sort required elements first.
+ * @param {boolean=} callbacksLast whether or not to sort callbacks after everything else.
+ * @param {boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements.
+ * @param {boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape.
+ * @param {boolean=} checkTypes whether or not sorting of prop type definitions are checked.
+ * @returns {Object|*|{range, text}} the sort order of the two elements.
+ */
+export function fixPropTypesSort(context: Context, fixer: Fixer, declarations: any[], ignoreCase?: boolean | undefined, requiredFirst?: boolean | undefined, callbacksLast?: boolean | undefined, noSortAlphabetically?: boolean | undefined, sortShapeProp?: boolean | undefined, checkTypes?: boolean | undefined): any | any | {
+    range;
+    text;
+};
+/**
+ * Checks if the proptype is a callback by checking if it starts with 'on'.
+ *
+ * @param {string} propName the name of the proptype to check.
+ * @returns {boolean} true if the proptype is a callback.
+ */
+export function isCallbackPropName(propName: string): boolean;
+/**
+ * Checks if the prop is required or not.
+ *
+ * @param {ASTNode} node the prop to check.
+ * @returns {boolean} true if the prop is required.
+ */
+export function isRequiredProp(node: ASTNode): boolean;
+/**
+ * Checks if the prop is PropTypes.shape.
+ *
+ * @param {ASTNode} node the prop to check.
+ * @returns {boolean} true if the prop is PropTypes.shape.
+ */
+export function isShapeProp(node: ASTNode): boolean;
+//# sourceMappingURL=propTypesSort.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"propTypesSort.d.ts","sourceRoot":"","sources":["propTypesSort.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;;;;GAaG;AACH,0CAXW,OAAO,SACP,KAAK,oCAEL,OAAO,8BACP,OAAO,8BACP,OAAO,qCACP,OAAO,8BACP,OAAO,2BACP,OAAO,eACL,YAAS;IAAC,KAAK,CAAC;IAAC,IAAI,CAAA;CAAC,CAyFlC;AA7LD;;;;;GAKG;AACH,6CAHW,MAAM,GACJ,OAAO,CAInB;AAlBD;;;;;GAKG;AACH,qCAHW,OAAO,GACL,OAAO,CAInB;AAYD;;;;;GAKG;AACH,kCAHW,OAAO,GACL,OAAO,CASnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propTypesSort.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,233 @@
+/**
+ * @fileoverview Common propTypes sorting functionality.
+ */
+
+'use strict';
+
+const toSorted = require('array.prototype.tosorted');
+
+const astUtil = require('./ast');
+const eslintUtil = require('./eslint');
+
+const getSourceCode = eslintUtil.getSourceCode;
+const getText = eslintUtil.getText;
+
+/**
+ * Returns the value name of a node.
+ *
+ * @param {ASTNode} node the node to check.
+ * @returns {string} The name of the node.
+ */
+function getValueName(node) {
+  return node.type === 'Property'
+    && node.value.property
+    && node.value.property.name;
+}
+
+/**
+ * Checks if the prop is required or not.
+ *
+ * @param {ASTNode} node the prop to check.
+ * @returns {boolean} true if the prop is required.
+ */
+function isRequiredProp(node) {
+  return getValueName(node) === 'isRequired';
+}
+
+/**
+ * Checks if the proptype is a callback by checking if it starts with 'on'.
+ *
+ * @param {string} propName the name of the proptype to check.
+ * @returns {boolean} true if the proptype is a callback.
+ */
+function isCallbackPropName(propName) {
+  return /^on[A-Z]/.test(propName);
+}
+
+/**
+ * Checks if the prop is PropTypes.shape.
+ *
+ * @param {ASTNode} node the prop to check.
+ * @returns {boolean} true if the prop is PropTypes.shape.
+ */
+function isShapeProp(node) {
+  return !!(
+    node
+    && node.callee
+    && node.callee.property
+    && node.callee.property.name === 'shape'
+  );
+}
+
+/**
+ * Returns the properties of a PropTypes.shape.
+ *
+ * @param {ASTNode} node the prop to check.
+ * @returns {Array} the properties of the PropTypes.shape node.
+ */
+function getShapeProperties(node) {
+  return node.arguments
+    && node.arguments[0]
+    && node.arguments[0].properties;
+}
+
+/**
+ * Compares two elements.
+ *
+ * @param {ASTNode} a the first element to compare.
+ * @param {ASTNode} b the second element to compare.
+ * @param {Context} context The context of the two nodes.
+ * @param {boolean=} ignoreCase whether or not to ignore case when comparing the two elements.
+ * @param {boolean=} requiredFirst whether or not to sort required elements first.
+ * @param {boolean=} callbacksLast whether or not to sort callbacks after everything else.
+ * @param {boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements.
+ * @returns {number} the sort order of the two elements.
+ */
+function sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically) {
+  const aKey = String(astUtil.getKeyValue(context, a));
+  const bKey = String(astUtil.getKeyValue(context, b));
+
+  if (requiredFirst) {
+    if (isRequiredProp(a) && !isRequiredProp(b)) {
+      return -1;
+    }
+    if (!isRequiredProp(a) && isRequiredProp(b)) {
+      return 1;
+    }
+  }
+
+  if (callbacksLast) {
+    if (isCallbackPropName(aKey) && !isCallbackPropName(bKey)) {
+      return 1;
+    }
+    if (!isCallbackPropName(aKey) && isCallbackPropName(bKey)) {
+      return -1;
+    }
+  }
+
+  if (!noSortAlphabetically) {
+    if (ignoreCase) {
+      return aKey.localeCompare(bKey);
+    }
+
+    if (aKey < bKey) {
+      return -1;
+    }
+    if (aKey > bKey) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+const commentnodeMap = new WeakMap(); // all nodes reference WeakMap for start and end range
+
+/**
+ * Fixes sort order of prop types.
+ *
+ * @param {Context} context the second element to compare.
+ * @param {Fixer} fixer the first element to compare.
+ * @param {Array} declarations The context of the two nodes.
+ * @param {boolean=} ignoreCase whether or not to ignore case when comparing the two elements.
+ * @param {boolean=} requiredFirst whether or not to sort required elements first.
+ * @param {boolean=} callbacksLast whether or not to sort callbacks after everything else.
+ * @param {boolean=} noSortAlphabetically whether or not to disable alphabetical sorting of the elements.
+ * @param {boolean=} sortShapeProp whether or not to sort propTypes defined in PropTypes.shape.
+ * @param {boolean=} checkTypes whether or not sorting of prop type definitions are checked.
+ * @returns {Object|*|{range, text}} the sort order of the two elements.
+ */
+function fixPropTypesSort(
+  context,
+  fixer,
+  declarations,
+  ignoreCase,
+  requiredFirst,
+  callbacksLast,
+  noSortAlphabetically,
+  sortShapeProp,
+  checkTypes
+) {
+  function sortInSource(allNodes, source) {
+    const originalSource = source;
+    const sourceCode = getSourceCode(context);
+    for (let i = 0; i < allNodes.length; i++) {
+      const node = allNodes[i];
+      let commentAfter = [];
+      let commentBefore = [];
+      let newStart = 0;
+      let newEnd = 0;
+      try {
+        commentBefore = sourceCode.getCommentsBefore(node);
+        commentAfter = sourceCode.getCommentsAfter(node);
+      } catch (e) { /**/ }
+
+      if (commentAfter.length === 0 || commentBefore.length === 0) {
+        newStart = node.range[0];
+        newEnd = node.range[1];
+      }
+
+      const firstCommentBefore = commentBefore[0];
+      if (commentBefore.length >= 1) {
+        newStart = firstCommentBefore.range[0];
+      }
+      const lastCommentAfter = commentAfter[commentAfter.length - 1];
+      if (commentAfter.length >= 1) {
+        newEnd = lastCommentAfter.range[1];
+      }
+      commentnodeMap.set(node, { start: newStart, end: newEnd, hasComment: true });
+    }
+    const nodeGroups = allNodes.reduce((acc, curr) => {
+      if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
+        acc.push([]);
+      } else {
+        acc[acc.length - 1].push(curr);
+      }
+      return acc;
+    }, [[]]);
+
+    nodeGroups.forEach((nodes) => {
+      const sortedAttributes = toSorted(
+        nodes,
+        (a, b) => sorter(a, b, context, ignoreCase, requiredFirst, callbacksLast, noSortAlphabetically)
+      );
+
+      const sourceCodeText = getText(context);
+      let separator = '';
+      source = nodes.reduceRight((acc, attr, index) => {
+        const sortedAttr = sortedAttributes[index];
+        const commentNode = commentnodeMap.get(sortedAttr);
+        let sortedAttrText = sourceCodeText.slice(commentNode.start, commentNode.end);
+        const sortedAttrTextLastChar = sortedAttrText[sortedAttrText.length - 1];
+        if (!separator && [';', ','].some((allowedSep) => sortedAttrTextLastChar === allowedSep)) {
+          separator = sortedAttrTextLastChar;
+        }
+        if (sortShapeProp && isShapeProp(sortedAttr.value)) {
+          const shape = getShapeProperties(sortedAttr.value);
+          if (shape) {
+            const attrSource = sortInSource(
+              shape,
+              originalSource
+            );
+            sortedAttrText = attrSource.slice(sortedAttr.range[0], sortedAttr.range[1]);
+          }
+        }
+        const sortedAttrTextVal = checkTypes && !sortedAttrText.endsWith(separator) ? `${sortedAttrText}${separator}` : sortedAttrText;
+        return `${acc.slice(0, commentnodeMap.get(attr).start)}${sortedAttrTextVal}${acc.slice(commentnodeMap.get(attr).end)}`;
+      }, source);
+    });
+    return source;
+  }
+
+  const source = sortInSource(declarations, getText(context));
+
+  const rangeStart = commentnodeMap.get(declarations[0]).start;
+  const rangeEnd = commentnodeMap.get(declarations[declarations.length - 1]).end;
+  return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
+}
+
+module.exports = {
+  fixPropTypesSort,
+  isCallbackPropName,
+  isRequiredProp,
+  isShapeProp,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,6 @@
+export function formatPropWrapperFunctions(propWrapperFunctions: any): string;
+export function getExactPropWrapperFunctions(context: any): Set<any>;
+export function getPropWrapperFunctions(context: any): Set<any>;
+export function isExactPropWrapperFunction(context: any, name: any): any;
+export function isPropWrapperFunction(context: any, name: any): any;
+//# sourceMappingURL=propWrapper.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"propWrapper.d.ts","sourceRoot":"","sources":["propWrapper.js"],"names":[],"mappings":"AA0CA,8EAUC;AArBD,qEAIC;AAhBD,gEAEC;AAgBD,yEAGC;AAjBD,oEAMC"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/propWrapper.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview Utility functions for propWrapperFunctions setting
+ */
+
+'use strict';
+
+const filter = require('es-iterator-helpers/Iterator.prototype.filter');
+const some = require('es-iterator-helpers/Iterator.prototype.some');
+
+function searchPropWrapperFunctions(name, propWrapperFunctions) {
+  const splitName = name.split('.');
+  return some(propWrapperFunctions.values(), (func) => {
+    if (splitName.length === 2 && func.object === splitName[0] && func.property === splitName[1]) {
+      return true;
+    }
+    return name === func || func.property === name;
+  });
+}
+
+function getPropWrapperFunctions(context) {
+  return new Set(context.settings.propWrapperFunctions || []);
+}
+
+function isPropWrapperFunction(context, name) {
+  if (typeof name !== 'string') {
+    return false;
+  }
+  const propWrapperFunctions = getPropWrapperFunctions(context);
+  return searchPropWrapperFunctions(name, propWrapperFunctions);
+}
+
+function getExactPropWrapperFunctions(context) {
+  const propWrapperFunctions = getPropWrapperFunctions(context);
+  const exactPropWrappers = filter(propWrapperFunctions.values(), (func) => func.exact === true);
+  return new Set(exactPropWrappers);
+}
+
+function isExactPropWrapperFunction(context, name) {
+  const exactPropWrappers = getExactPropWrapperFunctions(context);
+  return searchPropWrapperFunctions(name, exactPropWrappers);
+}
+
+function formatPropWrapperFunctions(propWrapperFunctions) {
+  return Array.from(propWrapperFunctions, (func) => {
+    if (func.object && func.property) {
+      return `'${func.object}.${func.property}'`;
+    }
+    if (func.property) {
+      return `'${func.property}'`;
+    }
+    return `'${func}'`;
+  }).join(', ');
+}
+
+module.exports = {
+  formatPropWrapperFunctions,
+  getExactPropWrapperFunctions,
+  getPropWrapperFunctions,
+  isExactPropWrapperFunction,
+  isPropWrapperFunction,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,55 @@
+/**
+ * Checks if the Identifier node passed in looks like a propTypes declaration.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @returns {boolean} `true` if the node is a propTypes declaration, `false` if not
+ */
+export function isPropTypesDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if the node passed in looks like a contextTypes declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a contextTypes declaration, `false` if not
+ */
+export function isContextTypesDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if the node passed in looks like a contextType declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a contextType declaration, `false` if not
+ */
+export function isContextTypeDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if the node passed in looks like a childContextTypes declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a childContextTypes declaration, `false` if not
+ */
+export function isChildContextTypesDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if the Identifier node passed in looks like a defaultProps declaration.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @returns {boolean} `true` if the node is a defaultProps declaration, `false` if not
+ */
+export function isDefaultPropsDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if we are declaring a display name
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean} True if we are declaring a display name, false if not.
+ */
+export function isDisplayNameDeclaration(node: ASTNode): boolean;
+/**
+ * Checks if the PropTypes MemberExpression node passed in declares a required propType.
+ * @param {ASTNode} propTypeExpression node to check. Must be a `PropTypes` MemberExpression.
+ * @returns {boolean} `true` if this PropType is required, `false` if not.
+ */
+export function isRequiredPropType(propTypeExpression: ASTNode): boolean;
+/**
+ * Returns the type arguments of a node or type parameters if type arguments are not available.
+ * @param {ASTNode} node The node to get the type arguments from.
+ * @returns {ASTNode} The type arguments or type parameters of the node.
+ */
+export function getTypeArguments(node: ASTNode): ASTNode;
+/**
+ * Returns the super type arguments of a node or super type parameters if type arguments are not available.
+ * @param {ASTNode} node The node to get the super type arguments from.
+ * @returns {ASTNode} The super type arguments or parameters of the node.
+ */
+export function getSuperTypeArguments(node: ASTNode): ASTNode;
+//# sourceMappingURL=props.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/props.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["props.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,6CAHW,OAAO,GACL,OAAO,CAUnB;AAED;;;;GAIG;AACH,gDAHW,OAAO,GACL,OAAO,CAUnB;AAED;;;;GAIG;AACH,+CAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,qDAHW,OAAO,GACL,OAAO,CAInB;AAED;;;;GAIG;AACH,gDAHW,OAAO,GACL,OAAO,CAKnB;AAED;;;;GAIG;AACH,+CAHW,OAAO,GACL,OAAO,CAcnB;AAED;;;;GAIG;AACH,uDAHW,OAAO,GACL,OAAO,CAKnB;AAED;;;;GAIG;AACH,uCAHW,OAAO,GACL,OAAO,CAOnB;AAED;;;;GAIG;AACH,4CAHW,OAAO,GACL,OAAO,CAOnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/props.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/props.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,130 @@
+/**
+ * @fileoverview Utility functions for props
+ */
+
+'use strict';
+
+const astUtil = require('./ast');
+
+/**
+ * Checks if the Identifier node passed in looks like a propTypes declaration.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @returns {boolean} `true` if the node is a propTypes declaration, `false` if not
+ */
+function isPropTypesDeclaration(node) {
+  if (node && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')) {
+    // Flow support
+    if (node.typeAnnotation && node.key.name === 'props') {
+      return true;
+    }
+  }
+  return astUtil.getPropertyName(node) === 'propTypes';
+}
+
+/**
+ * Checks if the node passed in looks like a contextTypes declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a contextTypes declaration, `false` if not
+ */
+function isContextTypesDeclaration(node) {
+  if (node && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')) {
+    // Flow support
+    if (node.typeAnnotation && node.key.name === 'context') {
+      return true;
+    }
+  }
+  return astUtil.getPropertyName(node) === 'contextTypes';
+}
+
+/**
+ * Checks if the node passed in looks like a contextType declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a contextType declaration, `false` if not
+ */
+function isContextTypeDeclaration(node) {
+  return astUtil.getPropertyName(node) === 'contextType';
+}
+
+/**
+ * Checks if the node passed in looks like a childContextTypes declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a childContextTypes declaration, `false` if not
+ */
+function isChildContextTypesDeclaration(node) {
+  return astUtil.getPropertyName(node) === 'childContextTypes';
+}
+
+/**
+ * Checks if the Identifier node passed in looks like a defaultProps declaration.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @returns {boolean} `true` if the node is a defaultProps declaration, `false` if not
+ */
+function isDefaultPropsDeclaration(node) {
+  const propName = astUtil.getPropertyName(node);
+  return (propName === 'defaultProps' || propName === 'getDefaultProps');
+}
+
+/**
+ * Checks if we are declaring a display name
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean} True if we are declaring a display name, false if not.
+ */
+function isDisplayNameDeclaration(node) {
+  switch (node.type) {
+    case 'ClassProperty':
+    case 'PropertyDefinition':
+      return node.key && node.key.name === 'displayName';
+    case 'Identifier':
+      return node.name === 'displayName';
+    case 'Literal':
+      return node.value === 'displayName';
+    default:
+      return false;
+  }
+}
+
+/**
+ * Checks if the PropTypes MemberExpression node passed in declares a required propType.
+ * @param {ASTNode} propTypeExpression node to check. Must be a `PropTypes` MemberExpression.
+ * @returns {boolean} `true` if this PropType is required, `false` if not.
+ */
+function isRequiredPropType(propTypeExpression) {
+  return propTypeExpression.type === 'MemberExpression'
+    && propTypeExpression.property.name === 'isRequired';
+}
+
+/**
+ * Returns the type arguments of a node or type parameters if type arguments are not available.
+ * @param {ASTNode} node The node to get the type arguments from.
+ * @returns {ASTNode} The type arguments or type parameters of the node.
+ */
+function getTypeArguments(node) {
+  if ('typeArguments' in node) {
+    return node.typeArguments;
+  }
+  return node.typeParameters;
+}
+
+/**
+ * Returns the super type arguments of a node or super type parameters if type arguments are not available.
+ * @param {ASTNode} node The node to get the super type arguments from.
+ * @returns {ASTNode} The super type arguments or parameters of the node.
+ */
+function getSuperTypeArguments(node) {
+  if ('superTypeArguments' in node) {
+    return node.superTypeArguments;
+  }
+  return node.superTypeParameters;
+}
+
+module.exports = {
+  isPropTypesDeclaration,
+  isContextTypesDeclaration,
+  isContextTypeDeclaration,
+  isChildContextTypesDeclaration,
+  isDefaultPropsDeclaration,
+  isDisplayNameDeclaration,
+  isRequiredPropType,
+  getTypeArguments,
+  getSuperTypeArguments,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+declare function _exports(context: any, message: any, messageId: any, data: any): void;
+export = _exports;
+//# sourceMappingURL=report.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/report.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["report.js"],"names":[],"mappings":"AAIiB,uFAOhB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/report.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/report.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/report.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,12 @@
+'use strict';
+
+const getMessageData = require('./message');
+
+module.exports = function report(context, message, messageId, data) {
+  context.report(
+    Object.assign(
+      getMessageData(messageId, message),
+      data
+    )
+  );
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+declare function _exports(context: any, components: any, utils: any): {
+    VariableDeclarator(node: any): void;
+    FunctionDeclaration: (node: ASTNode) => void;
+    ArrowFunctionExpression: (node: ASTNode) => void;
+    FunctionExpression: (node: ASTNode) => void;
+    'FunctionDeclaration:exit': () => void;
+    'ArrowFunctionExpression:exit': () => void;
+    'FunctionExpression:exit': () => void;
+    JSXSpreadAttribute(node: any): void;
+    'MemberExpression, OptionalMemberExpression'(node: any): void;
+    ObjectPattern(node: any): void;
+    'Program:exit'(): void;
+};
+export = _exports;
+//# sourceMappingURL=usedPropTypes.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"usedPropTypes.d.ts","sourceRoot":"","sources":["usedPropTypes.js"],"names":[],"mappings":"AAiTiB;;gCA2JJ,OAAO;oCAAP,OAAO;+BAAP,OAAO;;;;;;;;EA2HnB"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/usedPropTypes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,584 @@
+/**
+ * @fileoverview Common used propTypes detection functionality.
+ */
+
+'use strict';
+
+const values = require('object.values');
+
+const astUtil = require('./ast');
+const componentUtil = require('./componentUtil');
+const testReactVersion = require('./version').testReactVersion;
+const ast = require('./ast');
+const eslintUtil = require('./eslint');
+
+const getScope = eslintUtil.getScope;
+const getSourceCode = eslintUtil.getSourceCode;
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const LIFE_CYCLE_METHODS = ['componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate'];
+const ASYNC_SAFE_LIFE_CYCLE_METHODS = ['getDerivedStateFromProps', 'getSnapshotBeforeUpdate', 'UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillUpdate'];
+
+function createPropVariables() {
+  /** @type {Map<string, string[]>} Maps the variable to its definition. `props.a.b` is stored as `['a', 'b']` */
+  let propVariables = new Map();
+  let hasBeenWritten = false;
+  const stack = [{ propVariables, hasBeenWritten }];
+  return {
+    pushScope() {
+      // popVariables is not copied until first write.
+      stack.push({ propVariables, hasBeenWritten: false });
+    },
+    popScope() {
+      stack.pop();
+      propVariables = stack[stack.length - 1].propVariables;
+      hasBeenWritten = stack[stack.length - 1].hasBeenWritten;
+    },
+    /**
+     * Add a variable name to the current scope
+     * @param {string} name
+     * @param {string[]} allNames Example: `props.a.b` should be formatted as `['a', 'b']`
+     * @returns {Map<string, string[]>}
+     */
+    set(name, allNames) {
+      if (!hasBeenWritten) {
+        // copy on write
+        propVariables = new Map(propVariables);
+        Object.assign(stack[stack.length - 1], { propVariables, hasBeenWritten: true });
+        stack[stack.length - 1].hasBeenWritten = true;
+      }
+      return propVariables.set(name, allNames);
+    },
+    /**
+     * Get the definition of a variable.
+     * @param {string} name
+     * @returns {string[]} Example: `props.a.b` is represented by `['a', 'b']`
+     */
+    get(name) {
+      return propVariables.get(name);
+    },
+  };
+}
+
+/**
+ * Checks if the string is one of `props`, `nextProps`, or `prevProps`
+ * @param {string} name The AST node being checked.
+ * @returns {boolean} True if the prop name matches
+ */
+function isCommonVariableNameForProps(name) {
+  return name === 'props' || name === 'nextProps' || name === 'prevProps';
+}
+
+/**
+ * Checks if the component must be validated
+ * @param {Object} component The component to process
+ * @returns {boolean} True if the component must be validated, false if not.
+ */
+function mustBeValidated(component) {
+  return !!(component && !component.ignorePropsValidation);
+}
+
+/**
+ * Check if we are in a lifecycle method
+ * @param {object} context
+ * @param {ASTNode} node The AST node being checked.
+ * @param {boolean} checkAsyncSafeLifeCycles
+ * @return {boolean} true if we are in a class constructor, false if not
+ */
+function inLifeCycleMethod(context, node, checkAsyncSafeLifeCycles) {
+  let scope = getScope(context, node);
+  while (scope) {
+    if (scope.block && scope.block.parent && scope.block.parent.key) {
+      const name = scope.block.parent.key.name;
+
+      if (LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
+        return true;
+      }
+      if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(name) >= 0) {
+        return true;
+      }
+    }
+    scope = scope.upper;
+  }
+  return false;
+}
+
+/**
+ * Returns true if the given node is a React Component lifecycle method
+ * @param {ASTNode} node The AST node being checked.
+ * @param {boolean} checkAsyncSafeLifeCycles
+ * @return {boolean} True if the node is a lifecycle method
+ */
+function isNodeALifeCycleMethod(node, checkAsyncSafeLifeCycles) {
+  if (node.key) {
+    if (node.kind === 'constructor') {
+      return true;
+    }
+
+    const nodeKeyName = node.key.name;
+
+    if (typeof nodeKeyName !== 'string') {
+      return false;
+    }
+
+    if (LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
+      return true;
+    }
+    if (checkAsyncSafeLifeCycles && ASYNC_SAFE_LIFE_CYCLE_METHODS.indexOf(nodeKeyName) >= 0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * Returns true if the given node is inside a React Component lifecycle
+ * method.
+ * @param {ASTNode} node The AST node being checked.
+ * @param {boolean} checkAsyncSafeLifeCycles
+ * @return {boolean} True if the node is inside a lifecycle method
+ */
+function isInLifeCycleMethod(node, checkAsyncSafeLifeCycles) {
+  if (
+    (node.type === 'MethodDefinition' || node.type === 'Property')
+    && isNodeALifeCycleMethod(node, checkAsyncSafeLifeCycles)
+  ) {
+    return true;
+  }
+
+  if (node.parent) {
+    return isInLifeCycleMethod(node.parent, checkAsyncSafeLifeCycles);
+  }
+
+  return false;
+}
+
+/**
+ * Check if a function node is a setState updater
+ * @param {ASTNode} node a function node
+ * @return {boolean}
+ */
+function isSetStateUpdater(node) {
+  const unwrappedParentCalleeNode = astUtil.isCallExpression(node.parent)
+    && ast.unwrapTSAsExpression(node.parent.callee);
+
+  return unwrappedParentCalleeNode
+    && unwrappedParentCalleeNode.property
+    && unwrappedParentCalleeNode.property.name === 'setState'
+    // Make sure we are in the updater not the callback
+    && node.parent.arguments[0] === node;
+}
+
+function isPropArgumentInSetStateUpdater(context, node, name) {
+  if (typeof name !== 'string') {
+    return;
+  }
+  let scope = getScope(context, node);
+  while (scope) {
+    const unwrappedParentCalleeNode = scope.block
+      && astUtil.isCallExpression(scope.block.parent)
+      && ast.unwrapTSAsExpression(scope.block.parent.callee);
+    if (
+      unwrappedParentCalleeNode
+      && unwrappedParentCalleeNode.property
+      && unwrappedParentCalleeNode.property.name === 'setState'
+      // Make sure we are in the updater not the callback
+      && scope.block.parent.arguments[0].range[0] === scope.block.range[0]
+      && scope.block.parent.arguments[0].params
+      && scope.block.parent.arguments[0].params.length > 1
+    ) {
+      return scope.block.parent.arguments[0].params[1].name === name;
+    }
+    scope = scope.upper;
+  }
+  return false;
+}
+
+/**
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @returns {boolean}
+ */
+function isInClassComponent(context, node) {
+  return !!(componentUtil.getParentES6Component(context, node) || componentUtil.getParentES5Component(context, node));
+}
+
+/**
+ * Checks if the node is `this.props`
+ * @param {ASTNode|undefined} node
+ * @returns {boolean}
+ */
+function isThisDotProps(node) {
+  return !!node
+    && node.type === 'MemberExpression'
+    && ast.unwrapTSAsExpression(node.object).type === 'ThisExpression'
+    && node.property.name === 'props';
+}
+
+/**
+ * Checks if the prop has spread operator.
+ * @param {object} context
+ * @param {ASTNode} node The AST node being marked.
+ * @returns {boolean} True if the prop has spread operator, false if not.
+ */
+function hasSpreadOperator(context, node) {
+  const tokens = getSourceCode(context).getTokens(node);
+  return tokens.length && tokens[0].value === '...';
+}
+
+/**
+ * Checks if the node is a propTypes usage of the form `this.props.*`, `props.*`, `prevProps.*`, or `nextProps.*`.
+ * @param {Context} context
+ * @param {ASTNode} node
+ * @param {Object} utils
+ * @param {boolean} checkAsyncSafeLifeCycles
+ * @returns {boolean}
+ */
+function isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles) {
+  const unwrappedObjectNode = ast.unwrapTSAsExpression(node.object);
+
+  if (isInClassComponent(context, node)) {
+    // this.props.*
+    if (isThisDotProps(unwrappedObjectNode)) {
+      return true;
+    }
+    // props.* or prevProps.* or nextProps.*
+    if (
+      isCommonVariableNameForProps(unwrappedObjectNode.name)
+      && (inLifeCycleMethod(context, node, checkAsyncSafeLifeCycles) || astUtil.inConstructor(context, node))
+    ) {
+      return true;
+    }
+    // this.setState((_, props) => props.*))
+    if (isPropArgumentInSetStateUpdater(context, node, unwrappedObjectNode.name)) {
+      return true;
+    }
+    return false;
+  }
+  // props.* in function component
+  return unwrappedObjectNode.name === 'props' && !ast.isAssignmentLHS(node);
+}
+
+/**
+ * Retrieve the name of a property node
+ * @param {Context} context
+ * @param {ASTNode} node The AST node with the property.
+ * @param {Object} utils
+ * @param {boolean} checkAsyncSafeLifeCycles
+ * @return {string|undefined} the name of the property or undefined if not found
+ */
+function getPropertyName(context, node, utils, checkAsyncSafeLifeCycles) {
+  const property = node.property;
+  if (property) {
+    switch (property.type) {
+      case 'Identifier':
+        if (node.computed) {
+          return '__COMPUTED_PROP__';
+        }
+        return property.name;
+      case 'MemberExpression':
+        return;
+      case 'Literal':
+        // Accept computed properties that are literal strings
+        if (typeof property.value === 'string') {
+          return property.value;
+        }
+        // Accept number as well but only accept props[123]
+        if (typeof property.value === 'number') {
+          if (isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles)) {
+            return property.raw;
+          }
+        }
+        // falls through
+      default:
+        if (node.computed) {
+          return '__COMPUTED_PROP__';
+        }
+        break;
+    }
+  }
+}
+
+module.exports = function usedPropTypesInstructions(context, components, utils) {
+  const checkAsyncSafeLifeCycles = testReactVersion(context, '>= 16.3.0');
+
+  const propVariables = createPropVariables();
+  const pushScope = propVariables.pushScope;
+  const popScope = propVariables.popScope;
+
+  /**
+   * Mark a prop type as used
+   * @param {ASTNode} node The AST node being marked.
+   * @param {string[]} [parentNames]
+   */
+  function markPropTypesAsUsed(node, parentNames) {
+    parentNames = parentNames || [];
+    let type;
+    let name;
+    let allNames;
+    let properties;
+    switch (node.type) {
+      case 'OptionalMemberExpression':
+      case 'MemberExpression':
+        name = getPropertyName(context, node, utils, checkAsyncSafeLifeCycles);
+        if (name) {
+          allNames = parentNames.concat(name);
+          if (
+            // Match props.foo.bar, don't match bar[props.foo]
+            node.parent.type === 'MemberExpression'
+            && node.parent.object === node
+          ) {
+            markPropTypesAsUsed(node.parent, allNames);
+          }
+          // Handle the destructuring part of `const {foo} = props.a.b`
+          if (
+            node.parent.type === 'VariableDeclarator'
+            && node.parent.id.type === 'ObjectPattern'
+          ) {
+            node.parent.id.parent = node.parent; // patch for bug in eslint@4 in which ObjectPattern has no parent
+            markPropTypesAsUsed(node.parent.id, allNames);
+          }
+
+          // const a = props.a
+          if (
+            node.parent.type === 'VariableDeclarator'
+            && node.parent.id.type === 'Identifier'
+          ) {
+            propVariables.set(node.parent.id.name, allNames);
+          }
+          // Do not mark computed props as used.
+          type = name !== '__COMPUTED_PROP__' ? 'direct' : null;
+        }
+        break;
+      case 'ArrowFunctionExpression':
+      case 'FunctionDeclaration':
+      case 'FunctionExpression': {
+        if (node.params.length === 0) {
+          break;
+        }
+        type = 'destructuring';
+        const propParam = isSetStateUpdater(node) ? node.params[1] : node.params[0];
+        properties = propParam.type === 'AssignmentPattern'
+          ? propParam.left.properties
+          : propParam.properties;
+        break;
+      }
+      case 'ObjectPattern':
+        type = 'destructuring';
+        properties = node.properties;
+        break;
+      case 'TSEmptyBodyFunctionExpression':
+        break;
+      default:
+        throw new Error(`${node.type} ASTNodes are not handled by markPropTypesAsUsed`);
+    }
+
+    const component = components.get(utils.getParentComponent(node));
+    const usedPropTypes = (component && component.usedPropTypes) || [];
+    let ignoreUnusedPropTypesValidation = (component && component.ignoreUnusedPropTypesValidation) || false;
+
+    switch (type) {
+      case 'direct': {
+        // Ignore Object methods
+        if (name in Object.prototype) {
+          break;
+        }
+
+        const reportedNode = node.property;
+        usedPropTypes.push({
+          name,
+          allNames,
+          node: reportedNode,
+        });
+        break;
+      }
+      case 'destructuring': {
+        for (let k = 0, l = (properties || []).length; k < l; k++) {
+          if (hasSpreadOperator(context, properties[k]) || properties[k].computed) {
+            ignoreUnusedPropTypesValidation = true;
+            break;
+          }
+          const propName = ast.getKeyValue(context, properties[k]);
+
+          if (!propName || properties[k].type !== 'Property') {
+            break;
+          }
+
+          usedPropTypes.push({
+            allNames: parentNames.concat([propName]),
+            name: propName,
+            node: properties[k],
+          });
+
+          if (properties[k].value.type === 'ObjectPattern') {
+            markPropTypesAsUsed(properties[k].value, parentNames.concat([propName]));
+          } else if (properties[k].value.type === 'Identifier') {
+            propVariables.set(properties[k].value.name, parentNames.concat(propName));
+          }
+        }
+        break;
+      }
+      default:
+        break;
+    }
+
+    components.set(component ? component.node : node, {
+      usedPropTypes,
+      ignoreUnusedPropTypesValidation,
+    });
+  }
+
+  /**
+   * @param {ASTNode} node We expect either an ArrowFunctionExpression,
+   *   FunctionDeclaration, or FunctionExpression
+   */
+  function markDestructuredFunctionArgumentsAsUsed(node) {
+    const param = node.params && isSetStateUpdater(node) ? node.params[1] : node.params[0];
+
+    const destructuring = param && (
+      param.type === 'ObjectPattern'
+      || ((param.type === 'AssignmentPattern') && (param.left.type === 'ObjectPattern'))
+    );
+
+    if (destructuring && (components.get(node) || components.get(node.parent))) {
+      markPropTypesAsUsed(node);
+    }
+  }
+
+  function handleSetStateUpdater(node) {
+    if (!node.params || node.params.length < 2 || !isSetStateUpdater(node)) {
+      return;
+    }
+    markPropTypesAsUsed(node);
+  }
+
+  /**
+   * Handle both stateless functions and setState updater functions.
+   * @param {ASTNode} node We expect either an ArrowFunctionExpression,
+   *   FunctionDeclaration, or FunctionExpression
+   */
+  function handleFunctionLikeExpressions(node) {
+    pushScope();
+    handleSetStateUpdater(node);
+    markDestructuredFunctionArgumentsAsUsed(node);
+  }
+
+  function handleCustomValidators(component) {
+    const propTypes = component.declaredPropTypes;
+    if (!propTypes) {
+      return;
+    }
+
+    Object.keys(propTypes).forEach((key) => {
+      const node = propTypes[key].node;
+
+      if (node && node.value && astUtil.isFunctionLikeExpression(node.value)) {
+        markPropTypesAsUsed(node.value);
+      }
+    });
+  }
+
+  return {
+    VariableDeclarator(node) {
+      const unwrappedInitNode = ast.unwrapTSAsExpression(node.init);
+
+      // let props = this.props
+      if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context, node) && node.id.type === 'Identifier') {
+        propVariables.set(node.id.name, []);
+      }
+
+      // Only handles destructuring
+      if (node.id.type !== 'ObjectPattern' || !unwrappedInitNode) {
+        return;
+      }
+
+      // let {props: {firstname}} = this
+      const propsProperty = node.id.properties.find((property) => (
+        property.key
+        && (property.key.name === 'props' || property.key.value === 'props')
+      ));
+
+      if (unwrappedInitNode.type === 'ThisExpression' && propsProperty && propsProperty.value.type === 'ObjectPattern') {
+        markPropTypesAsUsed(propsProperty.value);
+        return;
+      }
+
+      // let {props} = this
+      if (unwrappedInitNode.type === 'ThisExpression' && propsProperty && propsProperty.value.name === 'props') {
+        propVariables.set('props', []);
+        return;
+      }
+
+      // let {firstname} = props
+      if (
+        isCommonVariableNameForProps(unwrappedInitNode.name)
+        && (utils.getParentStatelessComponent(node) || isInLifeCycleMethod(node, checkAsyncSafeLifeCycles))
+      ) {
+        markPropTypesAsUsed(node.id);
+        return;
+      }
+
+      // let {firstname} = this.props
+      if (isThisDotProps(unwrappedInitNode) && isInClassComponent(context, node)) {
+        markPropTypesAsUsed(node.id);
+        return;
+      }
+
+      // let {firstname} = thing, where thing is defined by const thing = this.props.**.*
+      if (propVariables.get(unwrappedInitNode.name)) {
+        markPropTypesAsUsed(node.id, propVariables.get(unwrappedInitNode.name));
+      }
+    },
+
+    FunctionDeclaration: handleFunctionLikeExpressions,
+
+    ArrowFunctionExpression: handleFunctionLikeExpressions,
+
+    FunctionExpression: handleFunctionLikeExpressions,
+
+    'FunctionDeclaration:exit': popScope,
+
+    'ArrowFunctionExpression:exit': popScope,
+
+    'FunctionExpression:exit': popScope,
+
+    JSXSpreadAttribute(node) {
+      const component = components.get(utils.getParentComponent(node));
+      components.set(component ? component.node : node, {
+        ignoreUnusedPropTypesValidation: node.argument.type !== 'ObjectExpression',
+      });
+    },
+
+    'MemberExpression, OptionalMemberExpression'(node) {
+      if (isPropTypesUsageByMemberExpression(context, node, utils, checkAsyncSafeLifeCycles)) {
+        markPropTypesAsUsed(node);
+        return;
+      }
+
+      const propVariable = propVariables.get(ast.unwrapTSAsExpression(node.object).name);
+      if (propVariable) {
+        markPropTypesAsUsed(node, propVariable);
+      }
+    },
+
+    ObjectPattern(node) {
+      // If the object pattern is a destructured props object in a lifecycle
+      // method -- mark it for used props.
+      if (isNodeALifeCycleMethod(node.parent.parent, checkAsyncSafeLifeCycles) && node.properties.length > 0) {
+        markPropTypesAsUsed(node.parent);
+      }
+    },
+
+    'Program:exit'() {
+      values(components.list())
+        .filter((component) => mustBeValidated(component))
+        .forEach((component) => {
+          handleCustomValidators(component);
+        });
+    },
+  };
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,38 @@
+/**
+ * Search a particular variable in a list
+ * @param {Array} variables The variables list.
+ * @param {string} name The name of the variable to search.
+ * @returns {boolean} True if the variable was found, false if not.
+ */
+export function findVariable(variables: any[], name: string): boolean;
+/**
+ * Find a variable by name in the current scope.
+ * @param {Object} context The current rule context.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @param  {string} name Name of the variable to look for.
+ * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
+ */
+export function findVariableByName(context: any, node: ASTNode, name: string): ASTNode | null;
+/**
+ * Find and return a particular variable in a list
+ * @param {Array} variables The variables list.
+ * @param {string} name The name of the variable to search.
+ * @returns {Object} Variable if the variable was found, null if not.
+ */
+export function getVariable(variables: any[], name: string): any;
+/**
+ * Searches for a variable in the given scope.
+ *
+ * @param {Object} context The current rule context.
+ * @param {ASTNode} node The node to start looking from.
+ * @param {string} name The name of the variable to search.
+ * @returns {Object | undefined} Variable if the variable was found, undefined if not.
+ */
+export function getVariableFromContext(context: any, node: ASTNode, name: string): any | undefined;
+/**
+ * Returns the latest definition of the variable.
+ * @param {Object} variable
+ * @returns {Object | undefined} The latest variable definition or undefined.
+ */
+export function getLatestVariableDefinition(variable: any): any | undefined;
+//# sourceMappingURL=variable.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/variable.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"variable.d.ts","sourceRoot":"","sources":["variable.js"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,qDAHW,MAAM,GACJ,OAAO,CAInB;AA0CD;;;;;;GAMG;AACH,uDAJW,OAAO,QACN,MAAM,GACL,OAAO,GAAC,IAAI,CAkBxB;AA/DD;;;;;GAKG;AACH,oDAHW,MAAM,OAKhB;AAED;;;;;;;GAOG;AACH,2DAJW,OAAO,QACP,MAAM,GACJ,MAAS,SAAS,CAsB9B;AA2BD;;;;GAIG;AACH,4DAFa,MAAS,SAAS,CAI9B"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/variable.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/variable.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/variable.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+/**
+ * @fileoverview Utility functions for React components detection
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const getScope = require('./eslint').getScope;
+
+/**
+ * Search a particular variable in a list
+ * @param {Array} variables The variables list.
+ * @param {string} name The name of the variable to search.
+ * @returns {boolean} True if the variable was found, false if not.
+ */
+function findVariable(variables, name) {
+  return variables.some((variable) => variable.name === name);
+}
+
+/**
+ * Find and return a particular variable in a list
+ * @param {Array} variables The variables list.
+ * @param {string} name The name of the variable to search.
+ * @returns {Object} Variable if the variable was found, null if not.
+ */
+function getVariable(variables, name) {
+  return variables.find((variable) => variable.name === name);
+}
+
+/**
+ * Searches for a variable in the given scope.
+ *
+ * @param {Object} context The current rule context.
+ * @param {ASTNode} node The node to start looking from.
+ * @param {string} name The name of the variable to search.
+ * @returns {Object | undefined} Variable if the variable was found, undefined if not.
+ */
+function getVariableFromContext(context, node, name) {
+  let scope = getScope(context, node);
+
+  while (scope) {
+    let variable = getVariable(scope.variables, name);
+
+    if (!variable && scope.childScopes.length) {
+      variable = getVariable(scope.childScopes[0].variables, name);
+
+      if (!variable && scope.childScopes[0].childScopes.length) {
+        variable = getVariable(scope.childScopes[0].childScopes[0].variables, name);
+      }
+    }
+
+    if (variable) {
+      return variable;
+    }
+    scope = scope.upper;
+  }
+  return undefined;
+}
+
+/**
+ * Find a variable by name in the current scope.
+ * @param {Object} context The current rule context.
+ * @param {ASTNode} node The node to check. Must be an Identifier node.
+ * @param  {string} name Name of the variable to look for.
+ * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
+ */
+function findVariableByName(context, node, name) {
+  const variable = getVariableFromContext(context, node, name);
+
+  if (!variable || !variable.defs[0] || !variable.defs[0].node) {
+    return null;
+  }
+
+  if (variable.defs[0].node.type === 'TypeAlias') {
+    return variable.defs[0].node.right;
+  }
+
+  if (variable.defs[0].type === 'ImportBinding') {
+    return variable.defs[0].node;
+  }
+
+  return variable.defs[0].node.init;
+}
+
+/**
+ * Returns the latest definition of the variable.
+ * @param {Object} variable
+ * @returns {Object | undefined} The latest variable definition or undefined.
+ */
+function getLatestVariableDefinition(variable) {
+  return variable.defs[variable.defs.length - 1];
+}
+
+module.exports = {
+  findVariable,
+  findVariableByName,
+  getVariable,
+  getVariableFromContext,
+  getLatestVariableDefinition,
+};
Index: frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,6 @@
+export function testReactVersion(context: any, semverRange: any): boolean;
+export function testFlowVersion(context: any, semverRange: any): boolean;
+export function resetWarningFlag(): void;
+export function resetDetectedVersion(): void;
+export function resetDefaultVersion(): void;
+//# sourceMappingURL=version.d.ts.map
Index: frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts.map
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/version.d.ts.map	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["version.js"],"names":[],"mappings":"AAmLA,0EAEC;AAED,yEAEC;AAvKD,yCAEC;AAID,6CAEC;AA6BD,4CAEC"}
Index: frontend/node_modules/eslint-plugin-react/lib/util/version.js
===================================================================
--- frontend/node_modules/eslint-plugin-react/lib/util/version.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-react/lib/util/version.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,194 @@
+/**
+ * @fileoverview Utility functions for React and Flow version configuration
+ * @author Yannick Croissant
+ */
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const resolve = require('resolve');
+const semver = require('semver');
+const error = require('./error');
+
+const ULTIMATE_LATEST_SEMVER = '999.999.999';
+
+let warnedForMissingVersion = false;
+
+function resetWarningFlag() {
+  warnedForMissingVersion = false;
+}
+
+let cachedDetectedReactVersion;
+
+function resetDetectedVersion() {
+  cachedDetectedReactVersion = undefined;
+}
+
+function resolveBasedir(contextOrFilename) {
+  if (contextOrFilename) {
+    const filename = typeof contextOrFilename === 'string' ? contextOrFilename : contextOrFilename.getFilename();
+    const dirname = path.dirname(filename);
+    try {
+      if (fs.statSync(filename).isFile()) {
+        // dirname must be dir here
+        return dirname;
+      }
+    } catch (err) {
+      // https://github.com/eslint/eslint/issues/11989
+      if (err.code === 'ENOTDIR') {
+        // virtual filename could be recursive
+        return resolveBasedir(dirname);
+      }
+    }
+  }
+  return process.cwd();
+}
+
+function convertConfVerToSemver(confVer) {
+  const fullSemverString = /^[0-9]+\.[0-9]+$/.test(confVer) ? `${confVer}.0` : confVer;
+  return semver.coerce(fullSemverString.split('.').map((part) => Number(part)).join('.'));
+}
+
+let defaultVersion = ULTIMATE_LATEST_SEMVER;
+
+function resetDefaultVersion() {
+  defaultVersion = ULTIMATE_LATEST_SEMVER;
+}
+
+function readDefaultReactVersionFromContext(context) {
+  // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings && context.settings.react && context.settings.react.defaultVersion) {
+    let settingsDefaultVersion = context.settings.react.defaultVersion;
+    if (typeof settingsDefaultVersion !== 'string') {
+      error(`Warning: default React version specified in eslint-pluigin-react-settings must be a string; got "${typeof settingsDefaultVersion}"`);
+    }
+    settingsDefaultVersion = String(settingsDefaultVersion);
+    const result = convertConfVerToSemver(settingsDefaultVersion);
+    if (result) {
+      defaultVersion = result.version;
+    } else {
+      error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${settingsDefaultVersion}”. Falling back to latest version as default.`);
+    }
+  } else {
+    defaultVersion = ULTIMATE_LATEST_SEMVER;
+  }
+}
+
+// TODO, semver-major: remove context fallback
+function detectReactVersion(context) {
+  if (cachedDetectedReactVersion) {
+    return cachedDetectedReactVersion;
+  }
+
+  const basedir = resolveBasedir(context);
+
+  try {
+    const reactPath = resolve.sync('react', { basedir });
+    const react = require(reactPath); // eslint-disable-line global-require, import/no-dynamic-require
+    cachedDetectedReactVersion = react.version;
+    return cachedDetectedReactVersion;
+  } catch (e) {
+    if (e.code === 'MODULE_NOT_FOUND') {
+      if (!warnedForMissingVersion) {
+        let sentence2 = 'Assuming latest React version for linting.';
+        if (defaultVersion !== ULTIMATE_LATEST_SEMVER) {
+          sentence2 = `Assuming default React version for linting: "${defaultVersion}".`;
+        }
+        error(`Warning: React version was set to "detect" in eslint-plugin-react settings, but the "react" package is not installed. ${sentence2}`);
+        warnedForMissingVersion = true;
+      }
+      cachedDetectedReactVersion = defaultVersion;
+      return cachedDetectedReactVersion;
+    }
+    throw e;
+  }
+}
+
+function getReactVersionFromContext(context) {
+  readDefaultReactVersionFromContext(context);
+  let confVer = defaultVersion;
+  // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings && context.settings.react && context.settings.react.version) {
+    let settingsVersion = context.settings.react.version;
+    if (settingsVersion === 'detect') {
+      settingsVersion = detectReactVersion(context);
+    }
+    if (typeof settingsVersion !== 'string') {
+      error(`Warning: React version specified in eslint-plugin-react-settings must be a string; got “${typeof settingsVersion}”`);
+    }
+    confVer = String(settingsVersion);
+  } else if (!warnedForMissingVersion) {
+    error('Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .');
+    warnedForMissingVersion = true;
+  }
+
+  const result = convertConfVerToSemver(confVer);
+  if (!result) {
+    error(`Warning: React version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);
+  }
+  return result ? result.version : defaultVersion;
+}
+
+// TODO, semver-major: remove context fallback
+function detectFlowVersion(context) {
+  const basedir = resolveBasedir(context);
+
+  try {
+    const flowPackageJsonPath = resolve.sync('flow-bin/package.json', { basedir });
+    const flowPackageJson = require(flowPackageJsonPath); // eslint-disable-line global-require, import/no-dynamic-require
+    return flowPackageJson.version;
+  } catch (e) {
+    if (e.code === 'MODULE_NOT_FOUND') {
+      error('Warning: Flow version was set to "detect" in eslint-plugin-react settings, '
+        + 'but the "flow-bin" package is not installed. Assuming latest Flow version for linting.');
+      return ULTIMATE_LATEST_SEMVER;
+    }
+    throw e;
+  }
+}
+
+function getFlowVersionFromContext(context) {
+  let confVer = defaultVersion;
+  // .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
+  if (context.settings.react && context.settings.react.flowVersion) {
+    let flowVersion = context.settings.react.flowVersion;
+    if (flowVersion === 'detect') {
+      flowVersion = detectFlowVersion(context);
+    }
+    if (typeof flowVersion !== 'string') {
+      error('Warning: Flow version specified in eslint-plugin-react-settings must be a string; '
+        + `got “${typeof flowVersion}”`);
+    }
+    confVer = String(flowVersion);
+  } else {
+    throw 'Could not retrieve flowVersion from settings'; // eslint-disable-line no-throw-literal
+  }
+
+  const result = convertConfVerToSemver(confVer);
+  if (!result) {
+    error(`Warning: Flow version specified in eslint-plugin-react-settings must be a valid semver version, or "detect"; got “${confVer}”`);
+  }
+  return result ? result.version : defaultVersion;
+}
+
+function test(semverRange, confVer) {
+  return semver.satisfies(confVer, semverRange);
+}
+
+function testReactVersion(context, semverRange) {
+  return test(semverRange, getReactVersionFromContext(context));
+}
+
+function testFlowVersion(context, semverRange) {
+  return test(semverRange, getFlowVersionFromContext(context));
+}
+
+module.exports = {
+  testReactVersion,
+  testFlowVersion,
+  resetWarningFlag,
+  resetDetectedVersion,
+  resetDefaultVersion,
+};
