Commit f76a050e authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '1-project-introduction' into 'master'

Resolve "Project introduction"

Closes #1

See merge request grin-global/grin-global-ui!1
parents 99fcb1c8 850ba234
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./tsconfig.json"
],
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"@typescript-eslint",
"@typescript-eslint/tslint",
"prefer-arrow"
],
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"node": {
"paths": ["src"]
}
}
},
"rules": {
"@typescript-eslint/adjacent-overload-signatures": "error",
"@typescript-eslint/array-type": "off",
"@typescript-eslint/ban-types": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "comma",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
},
"overrides": {
"interface": {
"multiline": {
"delimiter": "semi",
"requireLast": true
}
}
}
}
],
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-parameter-properties": "off",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-use-before-declare": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/prefer-for-of": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "off",
"arrow-parens": [
"error",
"always"
],
"camelcase": "off",
"capitalized-comments": "off",
"complexity": "off",
"constructor-super": "error",
"curly": "error",
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": [
"error",
"smart"
],
"guard-for-in": "error",
"id-match": "error",
"import/no-extraneous-dependencies": "off",
"import/no-internal-modules": "off",
"import/order": "off",
"indent": [2, 2, {"SwitchCase": 1}],
"no-restricted-imports": ["error", {"patterns": ["./*", "../*"]}],
"max-classes-per-file": [
"error",
10
],
"max-len": [
"error",
{
"code": 250
}
],
"new-parens": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-cond-assign": "error",
"no-console": "off",
"no-debugger": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
"no-eval": "error",
"no-extra-bind": "error",
"no-fallthrough": "off",
"no-invalid-this": "off",
"no-multiple-empty-lines": [
"error",
{
"max": 3
}
],
"no-new-func": "error",
"no-new-wrappers": "error",
"no-redeclare": "error",
"no-return-await": "error",
"no-sequences": "error",
"no-shadow": [
"off",
{
"hoist": "all"
}
],
"no-sparse-arrays": "error",
"no-template-curly-in-string": "error",
// "no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
// "no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-unused-expressions": "off",
"no-unused-labels": "error",
"no-var": "error",
"object-shorthand": "error",
"object-curly-spacing": ["error", "always"],
"one-var": [
"error",
"never"
],
"prefer-arrow/prefer-arrow-functions": [
"off",
{
"disallowPrototype": true,
"singleReturnOnly": false,
"classPropertiesAllowed": false
}
],
"prefer-const": "error",
"prefer-object-spread": "error",
"quotes": ["error", "single"],
"quote-props": [
"error",
"consistent-as-needed"
],
"radix": "error",
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"asyncArrow": "always",
"named": "never"
}
],
"spaced-comment": "error",
"use-isnan": "error",
"valid-typeof": "off",
"@typescript-eslint/tslint/config": [
"error",
{
"rules": {
"import-spacing": true,
"jsdoc-format": [
true,
"check-multiline-start"
],
"no-reference-import": true,
"one-line": [
true,
"check-catch",
"check-else",
"check-finally",
"check-open-brace",
"check-whitespace"
],
"prefer-conditional-expression": true,
"trailing-comma": [
true,
{
"esSpecCompliant": true,
"multiline": "always",
"singleline": "never"
}
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast",
"check-type-operator",
"check-rest-spread"
]
}
}
]
}
}
target/
coverage/
generated/
.awesome-typescript-loader-cache/
node_modules/
.idea
*.iml
react-loadable.json
*.snap
......@@ -5,3 +5,23 @@ The **GRIN-Global Client** is a set of tools for genebank
technicians, curators and managers to interact with the
[GG-CE Server](https://gitlab.croptrust.org/grin-global/grin-global-server) APIs.
## Development environment setup
Install `node 10.4.1` or higher:
```bash
sudo apt-get install nodejs
```
Install `yarn 1.21.1`:
```bash
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install yarn
```
Run `yarn run installAndLink` in the project root directory.
{
"lerna": "^3.20.2",
"version": "0.0.0",
"useWorkspaces": true,
"npmClient": "yarn"
}
{
"name": "grin-global-ui",
"version": "0.1.0",
"private": true,
"license": "Apache-2.0",
"homepage": "https://gitlab.croptrust.org/grin-global/grin-global-ui",
"repository": {
"type": "git",
"url": "https://gitlab.croptrust.org/grin-global/grin-global-ui"
},
"scripts": {
"installAndLink": "yarn install && lerna bootstrap",
"clean": "lerna run clean"
},
"workspaces": [
"packages/i18n",
"packages/client"
],
"devDependencies": {
"lerna": "^3.20.2"
},
"engines": {
"node": ">=10.4.1",
"yarn": "^1.21.1"
}
}
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}
],
"react"
],
"compact": false,
"retainLines": true,
"minified": false,
"inputSourceMap": false,
"sourceMaps": false,
"plugins": [
"transform-object-rest-spread",
"babel-plugin-syntax-dynamic-import",
[
"./react-loadable-custom/babel.js",
{
"importName": "utilities/CustomReactLoadable"
}
]
]
}
module.exports = {
preset: 'ts-jest',
snapshotSerializers: ['enzyme-to-json/serializer'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testRegex: '/test/.*\\.test.(ts|tsx)$',
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'#/(.*)': '<rootDir>/test/$1',
},
setupFiles: ['<rootDir>/test/setupTests.ts'],
collectCoverage: true,
collectCoverageFrom: ['src/**/*.{ts,tsx}']
};
{
"name": "@gringlobal/client",
"version": "0.0.1",
"license": "Apache-2.0",
"scripts": {
"clean": "rimraf target",
"test": "jest"
},
"dependencies": {
"axios": "^0.19.2",
"connected-react-router": "^6.6.1",
"cross-env": "^7.0.0",
"express": "^4.17.1",
"express-http-proxy": "^1.6.0",
"history": "^4.10.1",
"i18next": "^19.0.3",
"i18next-browser-languagedetector": "^4.0.1",
"i18next-express-middleware": "^1.9.1",
"i18next-sync-fs-backend": "^1.1.1",
"i18next-xhr-backend": "^3.2.2",
"immutability-helper": "^3.0.1",
"js-md5": "^0.7.3",
"path": "^0.12.7",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-i18next": "^11.3.1",
"react-loadable": "latest",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.5",
"redux-form": "^8.2.6",
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"url-template": "^2.0.8"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@types/enzyme": "^3.10.5",
"@types/jest": "^25.1.2",
"@types/node": "13.5.3",
"@types/react": "16.9.19",
"@types/react-router": "5.1.4",
"@types/react-router-dom": "5.1.3",
"@types/redux-form": "8.2.0",
"@types/webpack-env": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/eslint-plugin-tslint": "^2.17.0",
"@typescript-eslint/parser": "^2.18.0",
"awesome-typescript-loader": "^5.2.1",
"babel-eslint": "^10.0.3",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-react-transform": "^3.0.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-typescript": "^7.0.0-alpha.19",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "^7.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"enzyme-to-json": "^3.4.4",
"es-cookie": "^1.3.2",
"es6-promise": "^4.2.8",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.10.0",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prefer-arrow": "^1.1.7",
"eslint-plugin-react": "^7.18.0",
"fast-glob": "^3.1.1",
"file-system": "^2.2.2",
"html-webpack-exclude-assets-plugin": "0.0.7",
"html-webpack-plugin": "^3.2.0",
"i18next-scanner": "^2.10.3",
"jest": "^25.1.0",
"jsdom": "^16.1.0",
"jsdom-global": "^3.0.2",
"lerna": "^3.20.2",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"react-hot-loader": "^4.12.18",
"react-test-renderer": "^16.12.0",
"redux-mock-store": "^1.5.4",
"rimraf": "^3.0.1",
"script-ext-html-webpack-plugin": "^2.1.4",
"terser-webpack-plugin": "^2.3.4",
"ts-jest": "^25.2.0",
"ts-node": "^8.6.2",
"tslint": "^6.0.0",
"tslint-loader": "^3.5.4",
"tslint-react": "^4.2.0",
"typescript": "^3.7.5",
"webpack": "^4.41.5",
"webpack-chunk-hash": "^0.6.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.10.1",
"webpack-hot-middleware": "^2.25.0",
"webpack-manifest-plugin": "^2.2.0",
"webpack-merge": "^4.2.2"
}
}
'use strict';
// Copy of node_modules/react-loadable/lib/babel.js, MIT licensed.
// COPYRIGHT (c) 2017-present James Kyle <me@thejameskyle.com>
//
// Added our configurable source name
exports.__esModule = true;
exports.default = function(_ref) {
const t = _ref.types;
const template = _ref.template;
return {
visitor: {
ImportDeclaration: function ImportDeclaration(path) {
const source = path.node.source.value;
const opts = { importName: 'react-loadable', ...this.opts };
// use importName from opts, default to 'react-loader'
if (source !== opts.importName) {return;}
const defaultSpecifier = path.get('specifiers').find(function(specifier) {
return specifier.isImportDefaultSpecifier();
});
if (!defaultSpecifier) {return;}
const bindingName = defaultSpecifier.node.local.name;
const binding = path.scope.getBinding(bindingName);
binding.referencePaths.forEach(function(refPath) {
let callExpression = refPath.parentPath;
if (callExpression.isMemberExpression() && callExpression.node.computed === false && callExpression.get('property').isIdentifier({ name: 'Map' })) {
callExpression = callExpression.parentPath;
}
if (!callExpression.isCallExpression()) {return;}
const args = callExpression.get('arguments');
if (args.length !== 1) {throw callExpression.error;}
const options = args[0];
if (!options.isObjectExpression()) {return;}
const properties = options.get('properties');
const propertiesMap = {};
properties.forEach(function(property) {
const key = property.get('key');
propertiesMap[key.node.name] = property;
});
if (propertiesMap.webpack) {
// console.log('Babeling has webpack prop', propertiesMap);
return;
}
const loaderMethod = propertiesMap.loader.get('value');
const dynamicImports = [];
loaderMethod.traverse({
Import: function Import(path) {
dynamicImports.push(path.parentPath);
}
});
// console.log('Babeling dynamicImports', dynamicImports);
if (!dynamicImports.length) {return;}
propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('webpack'), t.arrowFunctionExpression([], t.arrayExpression(dynamicImports.map(function(dynamicImport) {
return t.callExpression(t.memberExpression(t.identifier('require'), t.identifier('resolveWeak')), [dynamicImport.get('arguments')[0].node]);
})))));
propertiesMap.loader.insertAfter(t.objectProperty(t.identifier('modules'), t.arrayExpression(dynamicImports.map(function(dynamicImport) {
return dynamicImport.get('arguments')[0].node;
}))));
});
}
}
};
};
import { CONFIGURE_APPLICATION, SET_LANG } from '@gringlobal/client/constants/applicationConfig';
export const configure = (config: object) => ({
type: CONFIGURE_APPLICATION,
payload: config,
});
export const receiveLang = (lang: string) => ({
type: SET_LANG,
payload: lang,
});
import * as cookies from 'es-cookie';
import * as jwt from 'jsonwebtoken';
import { call, put, select, takeEvery } from 'redux-saga/effects';
// action
import navigateTo from '@gringlobal/client/action/navigation';
// constants
import {
LOGIN_APP, LOGIN_USER,
LOGIN_APP_SAGA, LOGIN_USER_SAGA, LOGOUT_USER_SAGA,
} from '@gringlobal/client/constants/login';
import { ROLE_CLIENT } from '@gringlobal/client/constants/userRoles';
// service
import { LoginService } from '@gringlobal/client/service/LoginService';
// utilities
import { clearCookies, saveCookies } from '@gringlobal/client/utilities';
import { log } from '@gringlobal/client/utilities/debug';
import { configureBackendApi } from '@gringlobal/client/utilities/requestUtils';
export const loginSagas = [
takeEvery(LOGIN_APP_SAGA, loginAppSaga),
takeEvery(LOGIN_USER_SAGA, loginUserSaga),
takeEvery(LOGOUT_USER_SAGA, logoutUserSaga),
];
// #loginUser
export const loginUserAction = (username, password, needRedirect = false) => ({
type: LOGIN_USER_SAGA,
payload: {
username,
password,
needRedirect,
},
});
function* loginUserSaga(action) {
const { username, password, needRedirect } = action.payload;
// log('Trying login', username);
try {
yield call(loginRequest, username, password);
if (needRedirect) {
yield call(navigateTo, '/');
}
window.location.reload();
return false;
} catch (e) {
return ({ error: e, errorDescription: e.message });
}
}
function* loginRequest(username, password) {
const userData = yield call(LoginService.login, username, password);
const apiUrl = yield select((state) => state.applicationConfig.apiUrl);
const tokenData = jwt.decode(userData.access_token);
saveCookies(
{ access_token: userData.access_token, ...userData, ...tokenData },
userData.exp * 1000 || new Date().getTime() + userData.expires_in * 1000,
apiUrl,
);
return put(loginUserSuccess({ ...userData, ...tokenData