This commit is contained in:
Benjamin Gammaire
2020-08-25 12:11:08 +01:00
parent 000fc0de42
commit 8c0707981f
101 changed files with 6693 additions and 9320 deletions

1
.env
View File

@@ -1,2 +1 @@
REACT_APP_PLATFORM_VERSION=$npm_package_version REACT_APP_PLATFORM_VERSION=$npm_package_version
NODE_PATH=./

6
.eslintignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
functions
functions-cron
build
cypress
lib

View File

@@ -1,5 +1,6 @@
// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project
{ {
<<<<<<< HEAD
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"extends": [ "extends": [
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
@@ -12,14 +13,6 @@
"version": "detect" "version": "detect"
} }
}, },
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"modules": true
}
},
"rules": { "rules": {
"prettier/prettier": "warn", "prettier/prettier": "warn",
"@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-inferrable-types": "off",

View File

@@ -1,10 +0,0 @@
asset-manifest.json,1537949860342,0d87381706f5f063fe1f904b9b48417707c24b21a182d3ee99fa89aa32eb7916
index.html,1537949860341,0e551be718bd77c91037eea185f2f2cf153d96a0d895d82228ba17f17a885192
manifest.json,1537949850681,65d4aa66cd0b8963e88196705111adfccad2e8b80725b6a7a82ca8a9a0de8889
favicon.ico,1537949850677,eae62e993eb980ec8a25058c39d5a51feab118bd2100c4deebb2a9c158ec11f9
service-worker.js,1537949860446,db8526deabb1a10c3ad5db7712a840a9e585839d6f1d924a9392fb731d3b0dab
static/css/main.29266132.css.map,1537949860350,3d32ace435910b3df426d546fd9fe5c6403cf4581bc6c81785bfce6c569cc62d
static/css/main.29266132.css,1537949860350,0d737eb0d4b0dc1f4aea758db2c583db32d7497cc58bde8bb1ee92df806392fd
static/media/logo.5d5d9eef.svg,1537949860349,c0528de9db7d849577b204745e2df806c06fc14e27faf121c417111680b4280d
static/js/main.3248e625.js,1537949860349,550f3079b50bdde4f0151fc53edddf6b2a2852142e7c540b2f83c61c0c753dfb
static/js/main.3248e625.js.map,1537949860351,b0cd5913da52648efa5541fff343702ebd10b058a621a6f76325eaddb837d00a

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.vscode .vscode
.firebase/
# IntelliJ # IntelliJ
/.idea /.idea

View File

@@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}

View File

@@ -1,18 +0,0 @@
{
"linters": {
"src/**/*.{ts,tsx}": [
"tslint --project tsconfig.json --config tslint.json"
],
"cypress/**/*.{ts,tsx}": [
"tslint --project cypress/tsconfig.json --config tslint.json"
],
"*.{js,jsx}": [
"tslint"
],
"*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|graphql|mdx)": [
"prettier --write",
"git add"
]
},
"ignore": ["functions/**",".storybook/**",config/**]
}

View File

@@ -82,15 +82,15 @@ jobs:
- CI_NODE=1 - CI_NODE=1
<<: *_test_defaults <<: *_test_defaults
# Note, Travis jobs combined with regular stage scripts below # Note, Travis jobs combined with regular stage scripts below
# Deploy - Firebase deployment to dev/prod depending on branch # Deploy - Firebase deployment to dev/prod depending on branch on push (not PR)
- stage: Deploy Dev - stage: Deploy Dev
if: branch = master if: branch = master AND type = push
script: script:
# Note - syntax different from above as merging into array instead of creating key:value # Note - syntax different from above as merging into array instead of creating key:value
- *_predeploy_defaults - *_predeploy_defaults
- bash scripts/deploy.dev.sh - bash scripts/deploy.dev.sh
- stage: Deploy Production - stage: Deploy Production
if: branch = production if: branch = production AND type = push
script: script:
- *_predeploy_defaults - *_predeploy_defaults
- bash scripts/deploy.prod.sh - bash scripts/deploy.prod.sh

View File

@@ -1,13 +0,0 @@
The app has been ejected from create-react-app which makes upgrading more difficult.
The current method consists of:
1. Create a new project with create react app, making sure `--typescript` flag specified
2. Manually compare config/\*, package.json, tsconfig for changes
3. Update accordingly
4. Test
5. Fix
Note, currently there should be no explicit changes to
http://russelljanderson.com/updating-create-react-app/

86
config-overrides.js Normal file
View File

@@ -0,0 +1,86 @@
/*
Custom overrides for default create-react-app build
Automatically read by react-app-rewired scripts
Overrides:
- Add custom service worker behaviour
*/
//eslint-disable-next-line
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
module.exports = function override(config, env) {
config.plugins = config.plugins.map(plugin => {
if (plugin.constructor.name === 'GenerateSW') {
return swPlugin()
}
return plugin
})
return config
}
/**
* Custom service worker plugin
*/
function swPlugin() {
return new WorkboxWebpackPlugin.GenerateSW({
cacheId: 'oa',
clientsClaim: true,
skipWaiting: true,
// NOTE 2020-01-14 CC - Add support to cache firebase storage and map tiles
runtimeCaching: [
{
urlPattern: new RegExp(/.*\.(?:png|gif|jpg|jpeg|webp|svg).*/gi),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'oa-images',
fetchOptions: {
credentials: 'same-origin',
mode: 'cors',
},
backgroundSync: {
name: 'oa-images-background',
options: {
maxRetentionTime: 60 * 60 * 24,
},
},
expiration: {
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 1000,
},
matchOptions: {
ignoreSearch: true,
},
},
},
{
urlPattern: new RegExp('^https://firebasestorage\\.googleapis\\.com/'),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'oa-other',
fetchOptions: {
credentials: 'same-origin',
mode: 'cors',
},
matchOptions: {
ignoreSearch: true,
},
},
},
{
urlPattern: new RegExp('^https://firestore\\.googleapis\\.com/'),
handler: 'NetworkOnly',
},
],
// end update 2020-01-14
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: '/index.html',
navigateFallbackWhitelist: [/^(?!\/__)/],
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
})
}

View File

@@ -1,93 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const paths = require('./paths')
// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')]
const NODE_ENV = process.env.NODE_ENV
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.',
)
}
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
var dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv,
].filter(Boolean)
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
}),
)
}
})
// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd())
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter)
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration.
const REACT_APP = /^REACT_APP_/i
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key]
return env
},
{
// Useful for determining whether were running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl,
},
)
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key])
return env
}, {}),
}
return { raw, stringified }
}
module.exports = getClientEnvironment

View File

@@ -1,4 +0,0 @@
import enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
enzyme.configure({ adapter: new Adapter() })

View File

@@ -1,14 +0,0 @@
'use strict';
// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
// The output is always the same.
return 'cssTransform';
},
};

View File

@@ -1,30 +0,0 @@
'use strict';
const path = require('path');
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
module.exports = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
if (filename.match(/\.svg$/)) {
return `module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: (props) => ({
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: null,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
}),
};`;
}
return `module.exports = ${assetFilename};`;
},
};

View File

@@ -1,7 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
'use strict';
const tsJestPreprocessor = require('ts-jest/preprocessor');
module.exports = tsJestPreprocessor;

View File

@@ -1,89 +0,0 @@
'use strict';
const path = require('path');
const fs = require('fs');
const url = require('url');
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
const envPublicUrl = process.env.PUBLIC_URL;
function ensureSlash(inputPath, needsSlash) {
const hasSlash = inputPath.endsWith('/');
if (hasSlash && !needsSlash) {
return inputPath.substr(0, inputPath.length - 1);
} else if (!hasSlash && needsSlash) {
return `${inputPath}/`;
} else {
return inputPath;
}
}
const getPublicUrl = appPackageJson =>
envPublicUrl || require(appPackageJson).homepage;
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath(appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson);
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
return ensureSlash(servedUrl, true);
}
const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
];
// Resolve file paths in the same order as webpack
const resolveModule = (resolveFn, filePath) => {
const extension = moduleFileExtensions.find(extension =>
fs.existsSync(resolveFn(`${filePath}.${extension}`))
);
if (extension) {
return resolveFn(`${filePath}.${extension}`);
}
return resolveFn(`${filePath}.js`);
};
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('build'),
appPublic: resolveApp('public'),
appHtml: resolveApp('public/index.html'),
appIndexJs: resolveModule(resolveApp, 'src/index'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
};
module.exports.moduleFileExtensions = moduleFileExtensions;

View File

@@ -1,22 +0,0 @@
'use strict';
if (typeof Promise === 'undefined') {
// Rejection tracking prevents a common issue where React gets into an
// inconsistent state due to an error, but it gets swallowed by a Promise,
// and the user has no idea what causes React's erratic future behavior.
require('promise/lib/rejection-tracking').enable();
window.Promise = require('promise/lib/es6-extensions.js');
}
// fetch() polyfill for making API calls.
require('whatwg-fetch');
// Object.assign() is commonly used with React.
// It will use the native implementation if it's present and isn't buggy.
Object.assign = require('object-assign');
// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
// We don't polyfill it in the browser--this is user's responsibility.
if (process.env.NODE_ENV === 'test') {
require('raf').polyfill(global);
}

View File

@@ -1,291 +0,0 @@
"use strict";
const autoprefixer = require("autoprefixer");
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const getClientEnvironment = require("./env");
const paths = require("./paths");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
const publicPath = "/";
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
const publicUrl = "";
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
devtool: "cheap-module-source-map",
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
entry: [
// We ship a few polyfills by default:
require.resolve("./polyfills"),
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
require.resolve("react-dev-utils/webpackHotDevClient"),
// Finally, this is your app's code:
paths.appIndexJs
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
],
output: {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// This does not produce a real file. It's just the virtual path that is
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: "static/js/bundle.js",
// There are also additional JS chunk files if you use code splitting.
chunkFilename: "static/js/[name].chunk.js",
// This is the URL that app is served from. We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, "/")
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ["node_modules", paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebookincubator/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: [
".mjs",
".web.ts",
".ts",
".web.tsx",
".tsx",
".web.js",
".js",
".json",
".web.jsx",
".jsx"
],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
"react-native": "react-native-web"
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
new TsconfigPathsPlugin({ configFile: paths.appTsConfig })
]
},
module: {
strictExportPresence: true,
rules: [
// TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
// { parser: { requireEnsure: false } },
{
test: /\.(js|jsx|mjs)$/,
loader: require.resolve("source-map-loader"),
enforce: "pre",
include: paths.appSrc
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]"
}
},
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
options: {
compact: true
}
},
// Compile .tsx?
{
test: /\.(ts|tsx)$/,
include: paths.appSrc,
use: [
{
loader: require.resolve("ts-loader"),
options: {
// disable type checker - we will use it in fork plugin
transpileOnly: true
}
}
]
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use a plugin to extract that CSS to a file, but
// in development "style" loader enables hot editing of CSS.
{
test: /\.css$/,
use: [
require.resolve("style-loader"),
{
loader: require.resolve("css-loader"),
options: {
importLoaders: 1
}
},
{
loader: require.resolve("postcss-loader"),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: "postcss",
plugins: () => [
require("postcss-flexbugs-fixes"),
autoprefixer({
browsers: [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 9" // React doesn't support IE8 anyway
],
flexbox: "no-2009"
})
]
}
}
]
},
//--------------- *** CUSTOM Add SCSS Loaders ----------------------//
{
test: /\.scss$/,
loaders: [
require.resolve("style-loader"),
require.resolve("css-loader"),
require.resolve("sass-loader")
]
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
// ------------ *** CUSTOM added .scss to exclude
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/, /\.scss$/],
loader: require.resolve("file-loader"),
options: {
name: "static/media/[name].[hash:8].[ext]"
}
}
]
}
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
]
},
plugins: [
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml
}),
// Add module names to factory functions so they appear in browser profiler.
new webpack.NamedModulesPlugin(),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebookincubator/create-react-app/issues/240
new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebookincubator/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Perform type checking and linting in a separate process to speed up compilation
new ForkTsCheckerWebpackPlugin({
async: false,
watch: paths.appSrc,
tsconfig: paths.appTsConfig,
tslint: paths.appTsLint
})
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty"
},
// Turn off performance hints during development because we don't do any
// splitting or minification in interest of speed. These warnings become
// cumbersome.
performance: {
hints: false
}
};

View File

@@ -1,668 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const resolve = require('resolve')
const PnpWebpackPlugin = require('pnp-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const safePostCssParser = require('postcss-safe-parser')
const ManifestPlugin = require('webpack-manifest-plugin')
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
const paths = require('./paths')
const getClientEnvironment = require('./env')
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt')
const typescriptFormatter = require('react-dev-utils/typescriptFormatter')
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'
// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'
// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig)
// style files regexes
const cssRegex = /\.css$/
const cssModuleRegex = /\.module\.css$/
const sassRegex = /\.(scss|sass)$/
const sassModuleRegex = /\.module\.(scss|sass)$/
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development'
const isEnvProduction = webpackEnv === 'production'
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// In development, we always serve from the root. This makes config easier.
const publicPath = isEnvProduction
? paths.servedPath
: isEnvDevelopment && '/'
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './'
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = isEnvProduction
? publicPath.slice(0, -1)
: isEnvDevelopment && ''
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl)
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
options: Object.assign(
{},
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined,
),
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
],
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
},
},
].filter(Boolean)
if (preProcessor) {
loaders.push({
loader: require.resolve(preProcessor),
options: {
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
},
})
}
return loaders
}
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'eval-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[chunkhash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[chunkhash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},
optimization: {
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
new TerserPlugin({
terserOptions: {
parse: {
// we want terser to parse ecma 8 code. However, we don't want it
// to apply any minfication steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending futher investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
parallel: true,
// Enable file caching
cache: true,
sourceMap: shouldUseSourceMap,
}),
// This is only used in production mode
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
// `inline: false` forces the sourcemap to be output into a
// separate file
inline: false,
// `annotation: true` appends the sourceMappingURL to the end of
// the css file, helping the browser find the sourcemap
annotation: true,
}
: false,
},
}),
],
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
chunks: 'all',
name: false,
},
// Keep the runtime chunk separated to enable long term caching
// https://twitter.com/wSokra/status/969679223278505985
runtimeChunk: true,
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules'].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
// Adds support for installing with Plug'n'Play, leading to faster installs and adding
// guards against forgotten dependencies and such.
PnpWebpackPlugin,
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
resolveLoader: {
plugins: [
// Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
// from the current package.
PnpWebpackPlugin.moduleLoader(module),
],
},
module: {
strictExportPresence: true,
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|mjs|jsx)$/,
enforce: 'pre',
use: [
{
options: {
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides',
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo![path]',
},
},
},
],
],
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
cacheCompression: isEnvProduction,
compact: isEnvProduction,
},
},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
cacheCompression: isEnvProduction,
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
},
'sass-loader',
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
'sass-loader',
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined,
),
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
// In development, this will be an empty string.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: publicPath,
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
isEnvProduction &&
new WorkboxWebpackPlugin.GenerateSW({
cacheId: 'oa',
clientsClaim: true,
skipWaiting: true,
// NOTE 2020-01-14 CC - Add support to cache firebase storage and map tiles
runtimeCaching: [
{
urlPattern: new RegExp(/.*\.(?:png|gif|jpg|jpeg|webp|svg).*/gi),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'oa-images',
fetchOptions: {
credentials: 'same-origin',
mode: 'cors',
},
backgroundSync: {
name: 'oa-images-background',
options: {
maxRetentionTime: 60 * 60 * 24,
},
},
expiration: {
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 1000,
},
matchOptions: {
ignoreSearch: true,
},
},
},
{
urlPattern: new RegExp(
'^https://firebasestorage\\.googleapis\\.com/',
),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'oa-other',
fetchOptions: {
credentials: 'same-origin',
mode: 'cors',
},
matchOptions: {
ignoreSearch: true,
},
},
},
{
urlPattern: new RegExp('^https://firestore\\.googleapis\\.com/'),
handler: 'NetworkOnly',
},
],
// end update 2020-01-14
exclude: [/\.map$/, /asset-manifest\.json$/],
importWorkboxFrom: 'cdn',
navigateFallback: publicUrl + '/index.html',
navigateFallbackWhitelist: [/^(?!\/__)/],
navigateFallbackBlacklist: [
// Exclude URLs starting with /_, as they're likely an API call
new RegExp('^/_'),
// Exclude URLs containing a dot, as they're likely a resource in
// public/ and not a SPA route
new RegExp('/[^/]+\\.[^/]+$'),
],
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
async: false,
checkSyntacticErrors: true,
tsconfig: paths.appTsConfig,
compilerOptions: {
module: 'esnext',
moduleResolution: 'node',
resolveJsonModule: true,
isolatedModules: true,
noEmit: true,
jsx: 'preserve',
},
reportFiles: [
'**',
'!**/*.json',
'!**/__tests__/**',
'!**/?(*.)(spec|test).*',
'!**/src/setupProxy.*',
'!**/src/setupTests.*',
],
watch: paths.appSrc,
silent: true,
formatter: typescriptFormatter,
}),
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
performance: false,
}
}

View File

@@ -1,387 +0,0 @@
'use strict';
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const paths = require('./paths');
const getClientEnvironment = require('./env');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1);
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
}
// Note: defined here because it will be used more than once.
const cssFilename = 'static/css/[name].[contenthash:8].css';
// ExtractTextPlugin expects the build output to be flat.
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
// However, our output is structured with css, js and media folders.
// To have this structure working with relative paths, we have to use custom options.
const extractTextPluginOptions = shouldUseRelativeAssetPaths ? // Making sure that the publicPath goes back to to build folder.
{
publicPath: Array(cssFilename.split('/').length).join('../')
} :
{};
// This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file.
module.exports = {
// Don't attempt to continue if there are any errors.
bail: true,
// We generate sourcemaps in production. This is slow but gives good results.
// You can exclude the *.map files from the build during deployment.
devtool: shouldUseSourceMap ? 'source-map' : false,
// In production, we only want to load the polyfills and the app code.
entry: [require.resolve('./polyfills'), paths.appIndexJs],
output: {
// The build folder.
path: paths.appBuild,
// Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it.
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/'),
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebookincubator/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: [
'.mjs',
'.web.ts',
'.ts',
'.web.tsx',
'.tsx',
'.web.js',
'.js',
'.json',
'.web.jsx',
'.jsx',
],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
new TsconfigPathsPlugin({
configFile: paths.appTsProdConfig
}),
],
},
module: {
strictExportPresence: true,
rules: [
// TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
// { parser: { requireEnsure: false } },
{
test: /\.(js|jsx|mjs)$/,
loader: require.resolve('source-map-loader'),
enforce: 'pre',
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
compact: true,
},
},
// Compile .tsx?
{
test: /\.(ts|tsx)$/,
include: paths.appSrc,
use: [{
loader: require.resolve('ts-loader'),
options: {
// disable type checker - we will use it in fork plugin
transpileOnly: true,
configFile: paths.appTsProdConfig,
},
}, ],
},
// The notation here is somewhat confusing.
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader normally turns CSS into JS modules injecting <style>,
// but unlike in development configuration, we do something different.
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
// (second argument), then grabs the result CSS and puts it into a
// separate file in our build process. This way we actually ship
// a single CSS file in production instead of JS code injecting <style>
// tags. If you use code splitting, however, any async bundles will still
// use the "style" loader inside the async code so CSS from them won't be
// in the main CSS file.
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(
Object.assign({
fallback: {
loader: require.resolve('style-loader'),
options: {
hmr: false,
},
},
use: [{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
minimize: true,
sourceMap: shouldUseSourceMap,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},
extractTextPluginOptions
)
),
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
//--------------- *** CUSTOM Add SCSS Loaders ----------------------//
{
test: /\.scss$/,
loaders: [
require.resolve("style-loader"),
require.resolve("css-loader"),
require.resolve("sass-loader")
]
},
// "file" loader makes sure assets end up in the `build` folder.
// When you `import` an asset, you get its filename.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV was set to production here.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// Minify the code.
new UglifyJsPlugin({
uglifyOptions: {
parse: {
// we want uglify-js to parse ecma 8 code. However we want it to output
// ecma 5 compliant code, to avoid issues with older browsers, this is
// whey we put `ecma: 5` to the compress and output section
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
// Use multi-process parallel running to improve the build speed
// Default number of concurrent runs: os.cpus().length - 1
parallel: true,
// Enable file caching
cache: true,
sourceMap: shouldUseSourceMap,
}), // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
new ExtractTextPlugin({
filename: cssFilename,
}),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: 'asset-manifest.json',
}),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
logger(message) {
if (message.indexOf('Total precache size is') === 0) {
// This message occurs for every build and is a bit too noisy.
return;
}
if (message.indexOf('Skipping static resource') === 0) {
// This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612
return;
}
console.log(message);
},
minify: true,
// For unknown URLs, fallback to the index page
navigateFallback: publicUrl + '/index.html',
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Perform type checking and linting in a separate process to speed up compilation
new ForkTsCheckerWebpackPlugin({
async: false,
tsconfig: paths.appTsProdConfig,
tslint: paths.appTsLint,
}),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
};

View File

@@ -1,104 +0,0 @@
'use strict';
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const paths = require('./paths');
const fs = require('fs');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0';
module.exports = function(proxy, allowedHost) {
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated:
// https://github.com/facebook/create-react-app/issues/2271
// https://github.com/facebook/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable.
disableHostCheck:
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files.
compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting.
clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory.
// This is confusing because those files wont automatically be available in
// production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic,
// By default files from `contentBase` will not trigger a page reload.
watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true,
// It is important to tell WebpackDevServer to use the same "root" path
// as we specified in the config. In development, we always serve from /.
publicPath: '/',
// WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.hooks[...].tap` calls above.
quiet: true,
// Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebook/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports
// https://github.com/facebook/create-react-app/issues/1065
watchOptions: {
ignored: ignoredFiles(paths.appSrc),
},
// Enable HTTPS if the HTTPS environment variable is set to 'true'
https: protocol === 'https',
host,
overlay: false,
historyApiFallback: {
// Paths with dots should still use the history fallback.
// See https://github.com/facebook/create-react-app/issues/387.
disableDotRule: true,
},
public: allowedHost,
proxy,
before(app, server) {
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(app);
}
// This lets us fetch source contents from webpack for the error overlay
app.use(evalSourceMapMiddleware(server));
// This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware());
// This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if
// it used the same host and port.
// https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware());
},
};
};

View File

@@ -2,10 +2,10 @@ import { UserMenuItem } from '../support/commands'
describe('[Common]', () => { describe('[Common]', () => {
it('[Default Page]', () => { it('[Default Page]', () => {
cy.step('The home page is /how-to') cy.step('The home page is /academy')
cy.visit('/') cy.visit('/')
.url() .url()
.should('include', '/how-to') .should('include', '/academy')
}) })
it('[Not-Found Page]', () => { it('[Not-Found Page]', () => {

View File

@@ -116,7 +116,7 @@ describe('[How To]', () => {
cy.step('How-to has basic info') cy.step('How-to has basic info')
cy.get('[data-cy=how-to-basis]').then($summary => { cy.get('[data-cy=how-to-basis]').then($summary => {
expect($summary).to.contain('By howto_creator', 'Author') expect($summary).to.contain('By howto_creator', 'Author')
expect($summary).to.contain('Last edit:', 'Edit') expect($summary).to.contain('Last edit on', 'Edit')
expect($summary).to.contain('Make an interlocking brick', 'Title') expect($summary).to.contain('Make an interlocking brick', 'Title')
expect($summary).to.contain( expect($summary).to.contain(
'show you how to make a brick using the injection machine', 'show you how to make a brick using the injection machine',

View File

@@ -40,7 +40,7 @@ describe('[Settings]', () => {
.clear() .clear()
.type(info.country) .type(info.country)
cy.get('[data-cy=country') cy.get('[data-cy=country')
.find('.flag-option') .find('li')
.first() .first()
.click() .click()
} }

View File

@@ -1,6 +1,5 @@
import 'cypress-file-upload' import 'cypress-file-upload'
import { Firestore, Auth as AuthNative, firebase } from './db/firebase' import { Firestore, firebase } from './db/firebase'
import FileData = Cypress.FileData
export enum UserMenuItem { export enum UserMenuItem {
Profile = 'Profile', Profile = 'Profile',
@@ -10,7 +9,6 @@ export enum UserMenuItem {
declare global { declare global {
namespace Cypress { namespace Cypress {
// tslint:disable-next-line:interface-name
interface Chainable { interface Chainable {
login( login(
username: string, username: string,
@@ -163,17 +161,6 @@ const attachCustomCommands = (Cypress: Cypress.Cypress) => {
}) })
}) })
const resolveMimeType = (filePath: string) => {
const mimeTypeMapping = [['.jpg', 'image/jpg'], ['.png', 'image/png']]
const [_, mimeType]: any = mimeTypeMapping.find(([ext]) =>
filePath.endsWith(ext),
)
if (!mimeType) {
throw new Error(`Please define the mime type for ${filePath} here!`)
}
return mimeType
}
Cypress.Commands.add('toggleUserMenuOn', () => { Cypress.Commands.add('toggleUserMenuOn', () => {
Cypress.log({ displayName: 'OPEN_USER_MENU' }) Cypress.log({ displayName: 'OPEN_USER_MENU' })
cy.get('[data-cy=user-menu]').should('be.exist') cy.get('[data-cy=user-menu]').should('be.exist')

View File

@@ -4,7 +4,6 @@ import { IUserPPDB, ProfileTypeLabel } from '../../src/models/user_pp.models'
declare global { declare global {
namespace Chai { namespace Chai {
// tslint:disable-next-line:interface-name
interface Assertion { interface Assertion {
containSubset(expect: any): any containSubset(expect: any): any
eqHowtoStep(expect: any, index: number) eqHowtoStep(expect: any, index: number)

View File

@@ -12,7 +12,6 @@
// You can read more here: // You can read more here:
// https://on.cypress.io/configuration // https://on.cypress.io/configuration
// *********************************************************** // ***********************************************************
// tslint:disable-next-line
require('cypress-plugin-retries') require('cypress-plugin-retries')
import './hooks' import './hooks'
import './commands' import './commands'

View File

@@ -1,7 +0,0 @@
{
"extends": "../tslint.json",
"rules": {
"interface-name": false,
"no-console": false
}
}

View File

@@ -1,3 +0,0 @@
{
"extends": "./tslint.json"
}

View File

@@ -1,7 +1,6 @@
const endpoint = 'https://davehakkens.nl/wp-json/aam/v1/authenticate' const endpoint = 'https://davehakkens.nl/wp-json/aam/v1/authenticate'
import axios from 'axios' import axios from 'axios'
import { firebaseAdmin } from '../Firebase/admin' import { firebaseAdmin } from '../Firebase/admin'
// tslint:disable no-implicit-dependencies
import { IUser } from '../models' import { IUser } from '../models'
export const DHLogin = async (username: string, password: string) => { export const DHLogin = async (username: string, password: string) => {

View File

@@ -1,4 +1,3 @@
// tslint:disable no-implicit-dependencies
// import { IDBEndpoint } from '@OAModels/common.models' // import { IDBEndpoint } from '@OAModels/common.models'
import * as rtdb from './realtimeDB' import * as rtdb from './realtimeDB'
import * as firestore from './firestoreDB' import * as firestore from './firestoreDB'

View File

@@ -2,8 +2,8 @@ import * as functions from 'firebase-functions'
import { IDBEndpoint } from '../models' import { IDBEndpoint } from '../models'
import { db } from '../Firebase/firestoreDB' import { db } from '../Firebase/firestoreDB'
const USER_ENDPOINT: IDBEndpoint = 'v3_users' as any
// TODO - fix links to main repo and handle with prefix_endpoint type checking // TODO - fix links to main repo and handle with prefix_endpoint type checking
const USER_ENDPOINT = 'v3_users'
/** /**
* Example function to show how an automated email can be triggered * Example function to show how an automated email can be triggered

View File

@@ -1,8 +1,6 @@
import * as functions from 'firebase-functions' import * as functions from 'firebase-functions'
import { DBDoc, IDBEndpoint } from '../models' import { DBDoc, IDBEndpoint } from '../models'
const USERS_ENDPOINT: IDBEndpoint = 'v3_users' as any
// TODO - fix links to main repo and handle with prefix_endpoint type checking
const USERS_ENDPOINT = 'v3_users'
/** /**
* Automatically create user revision on update * Automatically create user revision on update

View File

@@ -1,4 +1,3 @@
// tslint:disable no-implicit-dependencies
// Models can be imported from the main package for use here // Models can be imported from the main package for use here
// NOTE 1 - this requires adjustment main src in package.json // NOTE 1 - this requires adjustment main src in package.json
// NOTE 2 - shorthand @OAModels notation defined in tsconfig // NOTE 2 - shorthand @OAModels notation defined in tsconfig

View File

@@ -14,6 +14,6 @@
}, },
"skipLibCheck": true "skipLibCheck": true
}, },
"references": [{ "path": "../src" }], "references": [{ "path": "../tsconfig.shared.json" }],
"include": ["src/**/*"] "include": ["src/**/*"]
} }

View File

@@ -837,9 +837,9 @@ diff@^3.2.0:
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
dot-prop@^5.1.0: dot-prop@^5.1.0:
version "5.1.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.1.0.tgz#bdd8c986a77b83e3fca524e53786df916cabbd8a" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb"
integrity sha512-n1oC6NBF+KM9oVXtjmen4Yo7HyAVWV2UUl50dCYJdw2924K6dX9bf9TTTWaKtYlRn0FEtxG27KS80ayVLixxJA== integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==
dependencies: dependencies:
is-obj "^2.0.0" is-obj "^2.0.0"
@@ -1746,9 +1746,9 @@ lodash.once@^4.0.0:
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.10, lodash@^4.17.14: lodash@^4.17.10, lodash@^4.17.14:
version "4.17.15" version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
log-update@^3.2.0: log-update@^3.2.0:
version "3.2.0" version "3.2.0"

View File

@@ -3,67 +3,69 @@
"version": "1.2.0", "version": "1.2.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "react-app-rewired start",
"build": "node scripts/build.js", "build": "react-app-rewired build",
"eject": "react-scripts eject",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx src --color",
"lint:fix": "npm run lint -- --fix",
"serve": "npx serve -s build",
"test": "node scripts/test.e2e.js", "test": "node scripts/test.e2e.js",
"functions": "cd functions && npm run --", "test:unit": "react-app-rewired test --env=jsdom",
"lint": "tslint --project tsconfig.json --config tslint.json",
"format": "npm run prettier -- --write",
"prettier": "prettier \"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|graphql|mdx)\"",
"validate": "npm run lint && npm run prettier -- --list-different",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook", "storybook:build": "build-storybook",
"analyze": "npm run build -- --stats & npx webpack-bundle-analyzer ./build/bundle-stats.json" "analyze": "npx cra-bundle-analyzer && npx open-cli build/report.html"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix",
"git add"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}, },
"eslintConfig": {},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"dependencies": { "dependencies": {
"@khanacademy/react-multi-select": "^0.3.3", "@khanacademy/react-multi-select": "^0.3.3",
"@material-ui/core": "3.7.1", "@material-ui/core": "3.7.1",
"@sentry/browser": "^4.6.3", "@sentry/browser": "^4.6.3",
"@uppy/core": "^1.0.0", "@uppy/core": "^1.0.0",
"@uppy/dashboard": "^1.8.3",
"@uppy/react": "^1.0.0", "@uppy/react": "^1.0.0",
"client-compress": "^2.1.2", "client-compress": "^2.1.2",
"css-loader": "1.0.0",
"date-fns": "^1.30.1", "date-fns": "^1.30.1",
"debounce": "^1.2.0", "debounce": "^1.2.0",
"dexie": "^2.0.4", "dexie": "^2.0.4",
"dompurify": "^1.0.10", "dompurify": "^1.0.10",
"file-loader": "2.0.0",
"final-form": "4.18.5", "final-form": "4.18.5",
"final-form-arrays": "^3.0.1", "final-form-arrays": "^3.0.1",
"final-form-calculate": "^1.3.1", "final-form-calculate": "^1.3.1",
"firebase": "^7.0.0", "firebase": "^7.0.0",
"geolocation-utils": "^1.2.1",
"google-oauth-jwt": "^0.2.0",
"googleapis": "^37.2.0",
"html-webpack-plugin": "4.0.0-alpha.2",
"identity-obj-proxy": "3.0.0",
"is-url": "^1.2.4", "is-url": "^1.2.4",
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.2",
"jest-resolve": "23.6.0",
"jest-watch-typeahead": "^0.2.1",
"leaflet": "^1.5.1", "leaflet": "^1.5.1",
"leaflet.markercluster": "^1.4.1", "leaflet.markercluster": "^1.4.1",
"lorem-ipsum": "^2.0.3", "lorem-ipsum": "^2.0.3",
"md5": "^2.2.1",
"mini-css-extract-plugin": "0.5.0",
"mobx": "4.9.2", "mobx": "4.9.2",
"mobx-react": "5.4.3", "mobx-react": "5.4.3",
"places.js": "^1.16.4", "places.js": "^1.16.4",
"pubsub-js": "^1.7.0", "pubsub-js": "^1.7.0",
"query-string": "^6.2.0", "react": "^16.13.1",
"react": "16.8.4",
"react-app-polyfill": "^0.2.1",
"react-datepicker": "^2.9.6", "react-datepicker": "^2.9.6",
"react-dev-utils": "^7.0.3", "react-dev-utils": "^7.0.3",
"react-dom": "16.8.4", "react-dom": "^16.13.1",
"react-dropzone": "^10.1.10", "react-dropzone": "^10.1.10",
"react-final-form": "6.3.0", "react-final-form": "6.3.0",
"react-final-form-arrays": "^3.1.1", "react-final-form-arrays": "^3.1.1",
@@ -91,14 +93,10 @@
"rebass": "4.0.6", "rebass": "4.0.6",
"styled-components": "4.1.3", "styled-components": "4.1.3",
"styled-system": "^4.0.0", "styled-system": "^4.0.0",
"url-loader": "1.1.2",
"wordpress-hash-node": "^1.0.0",
"yup": "^0.27.0" "yup": "^0.27.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.2.2",
"@cypress/webpack-preprocessor": "^4.1.0", "@cypress/webpack-preprocessor": "^4.1.0",
"@mdx-js/loader": "^1.5.1",
"@storybook/addon-actions": "^5.2.5", "@storybook/addon-actions": "^5.2.5",
"@storybook/addon-docs": "^5.2.5", "@storybook/addon-docs": "^5.2.5",
"@storybook/addon-info": "^5.2.5", "@storybook/addon-info": "^5.2.5",
@@ -106,14 +104,15 @@
"@storybook/addon-links": "^5.2.5", "@storybook/addon-links": "^5.2.5",
"@storybook/addons": "^5.2.5", "@storybook/addons": "^5.2.5",
"@storybook/react": "^5.2.5", "@storybook/react": "^5.2.5",
"@svgr/webpack": "4.1.0", "@storybook/theming": "^5.3.18",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/dompurify": "^0.0.32", "@types/dompurify": "^0.0.32",
"@types/flux-standard-action": "1.1.0", "@types/flux-standard-action": "1.1.0",
"@types/jest": "23.3.2",
"@types/md5": "^2.1.33",
"@types/node": "10.11.0", "@types/node": "10.11.0",
"@types/pubsub-js": "^1.5.18", "@types/pubsub-js": "^1.5.18",
"@types/react": "16.8.4", "@types/react": "^16.9.34",
"@types/react-dom": "16.8.4", "@types/react-dom": "16.8.4",
"@types/react-hamburger-menu": "^0.0.3", "@types/react-hamburger-menu": "^0.0.3",
"@types/react-leaflet": "^1.1.6", "@types/react-leaflet": "^1.1.6",
@@ -131,111 +130,27 @@
"@types/styled-components": "4.0", "@types/styled-components": "4.0",
"@types/styled-system": "^3.2.2", "@types/styled-system": "^3.2.2",
"@types/yup": "^0.26.27", "@types/yup": "^0.26.27",
"awesome-typescript-loader": "^5.2.1", "@typescript-eslint/eslint-plugin": "^2.29.0",
"babel-core": "7.0.0-bridge.0", "@typescript-eslint/parser": "^2.29.0",
"babel-eslint": "9.0.0",
"babel-jest": "23.6.0",
"babel-loader": "8.0.5",
"babel-plugin-named-asset-import": "^0.3.1",
"babel-preset-react-app": "^7.0.1",
"bfj": "6.1.1",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"chai-subset": "^1.6.0", "chai-subset": "^1.6.0",
"cra-bundle-analyzer": "^0.0.3",
"cross-env": "^6.0.3", "cross-env": "^6.0.3",
"cypress": "4.4.0", "cypress": "4.4.0",
"cypress-file-upload": "4.0.6", "cypress-file-upload": "4.0.6",
"cypress-plugin-retries": "^1.5.2", "cypress-plugin-retries": "^1.5.2",
"dotenv": "6.0.0", "dotenv": "6.0.0",
"dotenv-expand": "4.2.0", "eslint-config-prettier": "^6.10.1",
"enzyme": "^3.8.0", "eslint-plugin-prettier": "^3.1.3",
"enzyme-adapter-react-16": "^1.8.0",
"eslint": "5.12.0",
"eslint-config-react-app": "^3.0.7",
"eslint-loader": "2.1.1",
"eslint-plugin-flowtype": "2.50.1",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsx-a11y": "6.1.2",
"eslint-plugin-react": "7.12.4",
"fork-ts-checker-webpack-plugin-alt": "0.4.14",
"fs-extra": "7.0.1", "fs-extra": "7.0.1",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^8.1.5", "lint-staged": "^8.1.5",
"mobx-react-devtools": "^6.0.3", "mobx-react-devtools": "^6.0.3",
"node-sass": "^4.11.0", "node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pnp-webpack-plugin": "1.2.1",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.5.0",
"postcss-safe-parser": "4.0.1",
"prettier": "^1.16.4", "prettier": "^1.16.4",
"react-docgen-typescript-loader": "^3.3.0", "react-app-rewired": "^2.1.5",
"resolve": "1.10.0", "react-scripts": "3.4.1",
"sass-loader": "7.1.0",
"start-server-and-test": "^1.11.0", "start-server-and-test": "^1.11.0",
"style-loader": "0.23.1", "ts-loader": "^7.0.5",
"terser-webpack-plugin": "1.2.2", "typescript": "^3.7.4"
"ts-loader": "^6.1.1",
"tslint": "5.7.0",
"tslint-config-prettier": "1.10.0",
"tslint-react": "3.2.0",
"typescript": "^3.7.4",
"webpack": "4.28.3",
"webpack-dev-server": "3.1.14",
"webpack-manifest-plugin": "2.0.4",
"workbox-webpack-plugin": "4.3.1"
},
"resolutions": {
"@types/react": "16.8.4"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"resolver": "jest-pnp-resolver",
"setupFiles": [
"react-app-polyfill/jsdom"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node"
],
"watchPlugins": [
"<rootDir>/node_modules/jest-watch-typeahead/filename.js",
"<rootDir>/node_modules/jest-watch-typeahead/testname.js"
]
},
"babel": {
"presets": [
"react-app"
]
} }
} }

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -6,9 +6,19 @@
"src": "favicon.ico", "src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
} }
], ],
"start_url": "./index.html", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "background_color": "#ffffff"

3
public/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -53,11 +53,11 @@ function getCypressEnv(sharedEnv) {
} }
function randomString(length) { function randomString(length) {
var result = '' let result = ''
var characters = const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length const charactersLength = characters.length
for (var i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength)) result += characters.charAt(Math.floor(Math.random() * charactersLength))
} }
return result return result

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.78 12.9"><title>icon arrow down</title><g id="Calque_1" data-name="Calque 1"><path d="M12.61,5.23C11.2,6.48,9.81,7.75,8.4,9q-.35-3.9-.32-7.81a1.25,1.25,0,0,0-2.5,0q0,3.81.3,7.61C4.59,7.7,3.32,6.54,2.13,5.31S-.76,5.92.36,7.07a61.87,61.87,0,0,0,6.06,5.5,1.35,1.35,0,0,0,.53.25,1.2,1.2,0,0,0,1.24-.25c2.07-1.85,4.12-3.73,6.19-5.57C15.58,5.92,13.81,4.16,12.61,5.23Z" style="fill:#1d1d1b"/></g></svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1 @@
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 43.68 43.18"><title>icon download</title><path d="M25.93,36.6a60.76,60.76,0,0,0-5,4.64c-1.12,1.16.65,2.93,1.77,1.77a62.68,62.68,0,0,1,5-4.65c1.23-1-.54-2.79-1.77-1.76Z" style="fill:#1d1d1b"/><path d="M22.57,41.31l-5.35-4.63-1.51,2,.13.09c1.13,1.15,2.9-.62,1.77-1.77a2.15,2.15,0,0,0-.64-.48c-1.2-.58-2.66,1-1.52,2l5.35,4.63c1.22,1.05,3-.71,1.77-1.77Z" style="fill:#1d1d1b"/><path d="M23,40.26q-.39-8.16-.22-16.32a1.25,1.25,0,0,0-2.5,0q-.18,8.16.22,16.32c.08,1.6,2.58,1.61,2.5,0Z" style="fill:#1d1d1b"/><path d="M15.28,29.71c-4.11.86-11.13.23-12.54-4.63a8.36,8.36,0,0,1,.54-5.7c1.34-2.66,3.62-2.62,6.21-2.84,1.59-.13,1.61-2.63,0-2.5-2.81.24-5.62.33-7.54,2.7A10.61,10.61,0,0,0,.16,24.89c1.13,7,10.05,8.43,15.79,7.23,1.57-.33.9-2.74-.67-2.41Z" style="fill:#1d1d1b"/><path d="M10.74,15.29c-.25-5.81,2.89-11.4,9-12.59A10.3,10.3,0,0,1,32,12.19c.14,1.59,2.64,1.6,2.5,0C33.92,5,28.17-.51,20.79,0,12.85.62,7.91,7.73,8.24,15.29c.07,1.6,2.57,1.61,2.5,0Z" style="fill:#1d1d1b"/><path d="M33.28,13.44c5.68-.61,9.11,5.82,7.62,10.7-1.93,6.33-8.44,5.35-13.68,5.4a1.25,1.25,0,0,0,0,2.5c3.66,0,7.76.53,11.15-1.12a9.74,9.74,0,0,0,5.31-8c.63-5.92-3.87-12.66-10.4-12-1.58.17-1.6,2.67,0,2.5Z" style="fill:#1d1d1b"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -8,6 +8,7 @@ export const log = {
} }
export const Level = Sentry.Severity export const Level = Sentry.Severity
export const initErrorHandler = () => { export const initErrorHandler = () => {
const { location } = window
if ( if (
location.search.indexOf('noSentry=true') !== -1 || location.search.indexOf('noSentry=true') !== -1 ||
location.hostname === 'localhost' location.hostname === 'localhost'

View File

@@ -0,0 +1,12 @@
import * as React from 'react'
import { components } from 'react-select'
import { Image } from 'rebass'
import ArrowSelectIcon from '../../assets/icons/icon-arrow-select.svg'
// https://github.com/JedWatson/react-select/issues/685#issuecomment-420213835
export const DropdownIndicator = props =>
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
<Image src={ArrowSelectIcon} sx={{ width: '12px' }} />
</components.DropdownIndicator>
)

View File

@@ -3,7 +3,7 @@ import { Box } from 'rebass'
import checkmarkIcon from 'src/assets/icons/icon-checkmark.svg' import checkmarkIcon from 'src/assets/icons/icon-checkmark.svg'
interface IProps { interface IProps {
IconUrl: JSX.Element IconUrl: JSX.Element | string
height?: string height?: string
width?: string width?: string
ticked?: boolean ticked?: boolean

View File

@@ -1,5 +1,4 @@
import * as React from 'react' import * as React from 'react'
import { availableGlyphs } from '../Icons'
import { bytesToSize } from '../ImageInput/ImageInput' import { bytesToSize } from '../ImageInput/ImageInput'
import { IUploadedFileMeta } from 'src/stores/storage' import { IUploadedFileMeta } from 'src/stores/storage'
import { FileDetails } from './FileDetails' import { FileDetails } from './FileDetails'
@@ -9,60 +8,32 @@ interface IProps {
file: File | IUploadedFileMeta | null file: File | IUploadedFileMeta | null
allowDownload?: boolean allowDownload?: boolean
} }
interface IState {
glyph: availableGlyphs
size: string
}
const FileContainer = styled.a` const FileContainer = styled.a`
width: 300px; width: 300px;
margin: 3px 3px; margin: 3px 3px;
` `
export const FileInfo: React.FC<IProps> = ({ file, allowDownload }) => {
const meta = file as IUploadedFileMeta
const size = file ? bytesToSize(file.size) : '0'
export class FileInfo extends React.Component<IProps, IState> { if (!file) {
constructor(props: IProps) { return null
super(props)
this.state = {
glyph: props.file ? this.getGlyph(props.file.type) : 'image',
size: props.file ? bytesToSize(props.file.size) : '0',
}
} }
getGlyph(filetype: string) { return (
let glyph: availableGlyphs = 'image' <>
switch (filetype) { {allowDownload && meta.downloadUrl ? (
case 'application/pdf': <FileContainer
glyph = 'pdf' href={meta.downloadUrl}
break target="_blank"
default: download={file.name}
break >
} <FileDetails file={file} glyph="download-cloud" size={size} />
return glyph </FileContainer>
} ) : (
<FileDetails file={file} glyph="download-cloud" size={size} />
render() { )}
const { file, allowDownload } = this.props </>
const { glyph, size } = this.state )
const meta = file as IUploadedFileMeta
if (!file) {
return null
}
return (
<>
{allowDownload && meta.downloadUrl ? (
<FileContainer
href={meta.downloadUrl}
target="_blank"
download={file.name}
>
<FileDetails file={file} glyph={glyph} size={size} />
</FileContainer>
) : (
<FileDetails file={file} glyph={glyph} size={size} />
)}
</>
)
}
} }

View File

@@ -1,6 +1,5 @@
import * as React from 'react' import * as React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import theme from 'src/themes/styled.theme'
import { import {
Flex as RebassFlex, Flex as RebassFlex,
FlexProps as RebassFlexProps, FlexProps as RebassFlexProps,
@@ -62,7 +61,7 @@ export const BaseFlex = styled(RebassFlex)`
// TODO - incorporate custom css into rebass props to allow things like below to be passed // TODO - incorporate custom css into rebass props to allow things like below to be passed
export const Flex = (props: FlexProps) => ( export const Flex = (props: FlexProps) => (
<BaseFlex {...props}>{props.children}</BaseFlex> <BaseFlex {...(props as any)}>{props.children}</BaseFlex>
) )
export default Flex export default Flex

View File

@@ -6,6 +6,7 @@ import theme from 'src/themes/styled.theme'
import { Flex } from 'src/components/Flex' import { Flex } from 'src/components/Flex'
import { ErrorMessage, FieldContainer } from './elements' import { ErrorMessage, FieldContainer } from './elements'
import { IFieldProps } from './Fields' import { IFieldProps } from './Fields'
import { DropdownIndicator } from '../DropdownIndicator'
interface ISelectOption { interface ISelectOption {
value: string value: string
@@ -75,6 +76,14 @@ export const SelectStyles: Partial<Styles> = {
...provided, ...provided,
display: 'none', display: 'none',
}), }),
dropdownIndicator: (provided, state) => ({
...provided,
':hover': {
opacity: state.isFocused ? 1 : 0.5,
},
opacity: state.isFocused ? 1 : 0.3,
}),
} }
export const FilterStyles: Partial<Styles> = { export const FilterStyles: Partial<Styles> = {
@@ -98,10 +107,13 @@ export const FilterStyles: Partial<Styles> = {
border: '1px solid ' + theme.colors.blue, border: '1px solid ' + theme.colors.blue,
}, },
}), }),
placeholder: (provided, state) => ({
option: (provided, state) => ({
color: 'black',
...provided, ...provided,
color: 'black',
}),
option: (provided, state) => ({
...provided,
color: 'black',
backgroundColor: 'white', backgroundColor: 'white',
boxShadow: 'none', boxShadow: 'none',
':hover': { ':hover': {
@@ -165,6 +177,7 @@ const defaultProps: Partial<ISelectFieldProps> = {
getOptionValue: (option: ISelectOption) => option.value, getOptionValue: (option: ISelectOption) => option.value,
options: [], options: [],
} }
export const SelectField = ({ export const SelectField = ({
input, input,
meta, meta,
@@ -187,10 +200,11 @@ export const SelectField = ({
onCustomChange(getValueFromSelect(v as any)) onCustomChange(getValueFromSelect(v as any))
} }
}} }}
onBlur={input.onBlur} onBlur={input.onBlur as any}
onFocus={input.onFocus} onFocus={input.onFocus as any}
value={getValueForSelect(rest.options, input.value)} value={getValueForSelect(rest.options, input.value)}
classNamePrefix={'data-cy'} classNamePrefix={'data-cy'}
components={{ DropdownIndicator }}
{...defaultProps} {...defaultProps}
{...rest} {...rest}
/> />

View File

@@ -31,6 +31,16 @@ export const Input = styled.input<IFormElement>`
padding: 10px; padding: 10px;
` `
export const BlackPlaceholderInput = styled.input<IFormElement>`
${inputStyles};
height: 40px;
padding: 10px;
::placeholder {
color: black;
}
`
export const StyledDatePicker = styled(DatePicker)` export const StyledDatePicker = styled(DatePicker)`
${inputStyles}; ${inputStyles};
height: 40px; height: 40px;

View File

@@ -19,7 +19,7 @@ export const BaseHeading = styled(Text)`
type IHeadingProps = ITextProps & RebassHeadingProps type IHeadingProps = ITextProps & RebassHeadingProps
const Heading = (props: IHeadingProps) => ( const Heading = (props: IHeadingProps) => (
<BaseHeading {...props}>{props.children}</BaseHeading> <BaseHeading {...(props as any)}>{props.children}</BaseHeading>
) )
Heading.defaultProps = { Heading.defaultProps = {

View File

@@ -42,6 +42,7 @@ import {
import { FaSignal, FaFacebookF, FaSlack, FaInstagram } from 'react-icons/fa' import { FaSignal, FaFacebookF, FaSlack, FaInstagram } from 'react-icons/fa'
import { IconContext } from 'react-icons' import { IconContext } from 'react-icons'
import SVGs from './svgs' import SVGs from './svgs'
import { DownloadIcon } from './DownloadIcon'
interface IGlyphProps { interface IGlyphProps {
glyph: string glyph: string
@@ -56,6 +57,7 @@ interface IProps {
} }
export type availableGlyphs = export type availableGlyphs =
| 'download' | 'download'
| 'download-cloud'
| 'upload' | 'upload'
| 'add' | 'add'
| 'check' | 'check'
@@ -93,6 +95,7 @@ export type IGlyphs = { [k in availableGlyphs]: JSX.Element }
export const glyphs: IGlyphs = { export const glyphs: IGlyphs = {
download: <MdFileDownload />, download: <MdFileDownload />,
'download-cloud': <DownloadIcon />,
upload: <GoCloudUpload />, upload: <GoCloudUpload />,
add: <MdAdd />, add: <MdAdd />,
check: <MdCheck />, check: <MdCheck />,

View File

@@ -6,7 +6,7 @@
import React from 'react' import React from 'react'
import AlgoliaPlaces from 'places.js' import AlgoliaPlaces from 'places.js'
import { ALGOLIA_PLACES_CONFIG } from 'src/config/config' import { ALGOLIA_PLACES_CONFIG } from 'src/config/config'
import { Input } from '../Form/elements' import { Input, BlackPlaceholderInput } from '../Form/elements'
import { Observable, fromEvent, Subscription } from 'rxjs' import { Observable, fromEvent, Subscription } from 'rxjs'
import { debounceTime, map } from 'rxjs/operators' import { debounceTime, map } from 'rxjs/operators'
import styled from 'styled-components' import styled from 'styled-components'
@@ -18,7 +18,7 @@ interface IProps {
debounceTime: number debounceTime: number
onChange: (selected: ILocation) => void onChange: (selected: ILocation) => void
onClear?: () => void onClear?: () => void
styleVariant?: 'filter' | 'field' styleVariant?: 'filter' | 'field' | 'mapinput'
} }
interface IState { interface IState {
debouncedInputValue: string debouncedInputValue: string
@@ -70,7 +70,7 @@ export class LocationSearch extends React.Component<IProps, IState> {
appId: ALGOLIA_PLACES_CONFIG.applicationID, appId: ALGOLIA_PLACES_CONFIG.applicationID,
apiKey: ALGOLIA_PLACES_CONFIG.searchOnlyAPIKey, apiKey: ALGOLIA_PLACES_CONFIG.searchOnlyAPIKey,
container: this.placesInputRef.current, container: this.placesInputRef.current,
}).configure({ style: false, useDeviceLocation: false }) }).configure({ useDeviceLocation: false })
// add custom handler when place selected from list // add custom handler when place selected from list
this.places.on('change', (selected: IAlgoliaResponse) => this.places.on('change', (selected: IAlgoliaResponse) =>
this.handlePlaceSelectChange(selected), this.handlePlaceSelectChange(selected),
@@ -88,9 +88,9 @@ export class LocationSearch extends React.Component<IProps, IState> {
// when user changes input want to debounce and pass to places input // when user changes input want to debounce and pass to places input
subscribeToInputChanges() { subscribeToInputChanges() {
const observable: Observable< const observable: Observable<React.ChangeEvent<
React.ChangeEvent<HTMLInputElement> HTMLInputElement
> = fromEvent(this.userInputRef.current, 'keyup') >> = fromEvent(this.userInputRef.current, 'keyup')
this.inputValue$ = observable this.inputValue$ = observable
.pipe( .pipe(
map(e => e.currentTarget.value), map(e => e.currentTarget.value),
@@ -146,12 +146,25 @@ export class LocationSearch extends React.Component<IProps, IState> {
width: '100%', width: '100%',
}} }}
> >
<Input {/* Placeholder cannot be changed with inline notation (e.g.in FilterStyle or SelectStyle)
placeholder={this.props.placeholder} So created a new Styled component
style={styleVariant === 'filter' ? FilterStyle : SelectorStyle} */}
ref={this.userInputRef} {styleVariant !== 'mapinput' && (
onBlur={() => this.places.close()} <Input
/> placeholder={this.props.placeholder}
style={styleVariant === 'filter' ? FilterStyle : SelectorStyle}
ref={this.userInputRef}
onBlur={() => this.places.close()}
/>
)}
{styleVariant === 'mapinput' && (
<BlackPlaceholderInput
placeholder={this.props.placeholder}
style={FilterStyle}
ref={this.userInputRef}
onBlur={() => this.places.close()}
/>
)}
</span> </span>
{/* the second input takes debounced value from the first input and binds to algolia search */} {/* the second input takes debounced value from the first input and binds to algolia search */}
<AlgoliaResults <AlgoliaResults

View File

@@ -45,10 +45,7 @@ const MoreModalContainer = styled(Box)`
` `
export const MoreContainer = (props: BoxProps) => ( export const MoreContainer = (props: BoxProps) => (
<MoreModalContainer {...props}> <MoreModalContainer {...(props as any)}>{props.children}</MoreModalContainer>
{/* <MoreText color={'black'}>{props.text}</MoreText> */}
{props.children}
</MoreModalContainer>
) )
export default MoreContainer export default MoreContainer

View File

@@ -101,7 +101,7 @@ export class ProfileModal extends React.Component<IProps> {
<Flex> <Flex>
<ModalLink <ModalLink
onClick={() => this.logout()} onClick={() => this.logout()}
to={location.pathname} to={window.location.pathname}
data-cy="menu-Logout" data-cy="menu-Logout"
> >
<Flex color="rgba(27,27,27,0.5)">Log out</Flex> <Flex color="rgba(27,27,27,0.5)">Log out</Flex>

View File

@@ -6,6 +6,7 @@ import { FieldRenderProps } from 'react-final-form'
import Select from 'react-select' import Select from 'react-select'
import { SelectStyles, FilterStyles } from '../Form/Select.field' import { SelectStyles, FilterStyles } from '../Form/Select.field'
import { FieldContainer } from '../Form/elements' import { FieldContainer } from '../Form/elements'
import { DropdownIndicator } from '../DropdownIndicator'
// we include props from react-final-form fields so it can be used as a custom field component // we include props from react-final-form fields so it can be used as a custom field component
export interface IProps extends FieldRenderProps<any, any> { export interface IProps extends FieldRenderProps<any, any> {
@@ -71,6 +72,7 @@ class TagsSelect extends React.Component<IProps, IState> {
return ( return (
<FieldContainer data-cy="tag-select"> <FieldContainer data-cy="tag-select">
<Select <Select
components={{ DropdownIndicator }}
styles={styleVariant === 'selector' ? SelectStyles : FilterStyles} styles={styleVariant === 'selector' ? SelectStyles : FilterStyles}
isMulti isMulti
options={categoryTags} options={categoryTags}

View File

@@ -19,8 +19,7 @@ export interface ITextProps {
medium?: boolean medium?: boolean
small?: boolean small?: boolean
superSmall?: boolean superSmall?: boolean
// keyof colors returns full object prototype, include typeof for just named keys (i.e. color list) color?: string
color?: keyof typeof theme.colors
// clip forces text to fill max 1 line and add '...' for overflow // clip forces text to fill max 1 line and add '...' for overflow
clipped?: boolean clipped?: boolean
preLine?: boolean preLine?: boolean
@@ -150,7 +149,7 @@ type TextProps = ITextProps & RebassTextProps
// TODO - incorporate custom css into rebass props to allow things like below to be passed // TODO - incorporate custom css into rebass props to allow things like below to be passed
export const Text = (props: TextProps) => ( export const Text = (props: TextProps) => (
<BaseText {...props}>{props.children}</BaseText> <BaseText {...(props as any)}>{props.children}</BaseText>
) )
export default Text export default Text

View File

@@ -8,6 +8,7 @@ import {
CellMeasurer, CellMeasurer,
ListRowProps, ListRowProps,
} from 'react-virtualized' } from 'react-virtualized'
import { emStringToPx } from 'src/utils/helpers'
interface IProps { interface IProps {
data: any[] data: any[]
@@ -130,10 +131,7 @@ export class VirtualizedFlex extends React.Component<IProps, IState> {
} }
static defaultProps: IProps = { static defaultProps: IProps = {
widthBreakpoints: themes.breakpoints.map( widthBreakpoints: themes.breakpoints.map(emStringToPx),
// Convert theme em string to px number
width => Number(width.replace('em', '')) * 16,
),
data: [], data: [],
renderItem: data => <div>RenderItem {data}</div>, renderItem: data => <div>RenderItem {data}</div>,
} }

View File

@@ -75,7 +75,7 @@ if (siteVariant === 'production') {
searchOnlyAPIKey: e.REACT_APP_ALGOLIA_PLACES_API_KEY as string, searchOnlyAPIKey: e.REACT_APP_ALGOLIA_PLACES_API_KEY as string,
} }
// disable console logs // disable console logs
// tslint:disable no-empty // eslint-disable-next-line
console.log = () => {} console.log = () => {}
} }
@@ -122,7 +122,6 @@ export const FIREBASE_CONFIG = firebaseConfigs[siteVariant]
export const ALGOLIA_SEARCH_CONFIG = algoliaSearchConfig export const ALGOLIA_SEARCH_CONFIG = algoliaSearchConfig
export const ALGOLIA_PLACES_CONFIG = algoliaPlacesConfig export const ALGOLIA_PLACES_CONFIG = algoliaPlacesConfig
export const SENTRY_CONFIG = sentryConfig export const SENTRY_CONFIG = sentryConfig
// tslint:disable no-var-requires
export const VERSION = require('../../package.json').version export const VERSION = require('../../package.json').version
export const GA_TRACKING_ID = process.env.REACT_APP_GA_TRACKING_ID export const GA_TRACKING_ID = process.env.REACT_APP_GA_TRACKING_ID

View File

@@ -29,7 +29,7 @@ interface IState {
selectedDate: any selectedDate: any
isLocationSelected?: boolean isLocationSelected?: boolean
} }
interface IProps extends RouteComponentProps<any> {} type IProps = RouteComponentProps<any>
interface IInjectedProps extends IProps { interface IInjectedProps extends IProps {
eventStore: EventStore eventStore: EventStore
} }
@@ -105,7 +105,6 @@ export class EventsCreate extends React.Component<IProps, IState> {
errors, errors,
handleSubmit, handleSubmit,
}) => { }) => {
const disabled = invalid || submitting
return ( return (
<Flex mx={-2} bg={'inherit'} flexWrap="wrap"> <Flex mx={-2} bg={'inherit'} flexWrap="wrap">
<Flex bg="inherit" px={2} width={[1, 1, 2 / 3]} mt={4}> <Flex bg="inherit" px={2} width={[1, 1, 2 / 3]} mt={4}>

View File

@@ -1,4 +1,4 @@
import React from 'react' import React, { FC } from 'react'
import TagDisplay from 'src/components/Tags/TagDisplay/TagDisplay' import TagDisplay from 'src/components/Tags/TagDisplay/TagDisplay'
import { format } from 'date-fns' import { format } from 'date-fns'
import { IHowtoDB } from 'src/models/howto.models' import { IHowtoDB } from 'src/models/howto.models'
@@ -13,9 +13,9 @@ import TimeNeeded from 'src/assets/icons/icon-time-needed.svg'
import DifficultyLevel from 'src/assets/icons/icon-difficulty-level.svg' import DifficultyLevel from 'src/assets/icons/icon-difficulty-level.svg'
import { Button } from 'src/components/Button' import { Button } from 'src/components/Button'
import { IUser } from 'src/models/user.models' import { IUser } from 'src/models/user.models'
import { isAllowToEditContent } from 'src/utils/helpers' import { isAllowToEditContent, emStringToPx } from 'src/utils/helpers'
import theme from 'src/themes/styled.theme' import theme from 'src/themes/styled.theme'
import ElWithBeforeIcon from 'src/components/ElWithBeforeIcon' import ArrowIcon from 'src/assets/icons/icon-arrow-select.svg'
interface IProps { interface IProps {
howto: IHowtoDB howto: IHowtoDB
@@ -38,7 +38,7 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
const lastModifiedDate = format(new Date(howto._modified), 'DD-MM-YYYY') const lastModifiedDate = format(new Date(howto._modified), 'DD-MM-YYYY')
const creationDate = format(new Date(howto._created), 'DD-MM-YYYY') const creationDate = format(new Date(howto._created), 'DD-MM-YYYY')
if (lastModifiedDate !== creationDate) { if (lastModifiedDate !== creationDate) {
return 'Last edit: ' + format(new Date(howto._modified), 'DD-MM-YYYY') return 'Last edit on ' + format(new Date(howto._modified), 'DD-MM-YYYY')
} else { } else {
return '' return ''
} }
@@ -47,6 +47,8 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
public render() { public render() {
const { howto, loggedInUser } = this.props const { howto, loggedInUser } = this.props
const iconFlexDirection =
emStringToPx(theme.breakpoints[0]) > window.innerWidth ? 'column' : 'row'
return ( return (
<Flex <Flex
data-cy="how-to-basis" data-cy="how-to-basis"
@@ -65,8 +67,14 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
<Flex px={4} py={4} flexDirection={'column'} width={[1, 1, 1 / 2]}> <Flex px={4} py={4} flexDirection={'column'} width={[1, 1, 1 / 2]}>
<Flex justifyContent={'space-between'}> <Flex justifyContent={'space-between'}>
<Link to={'/how-to/'}> <Link to={'/how-to/'}>
<Button variant={'secondary'} data-cy={'go-back'}> <Button variant="subtle" fontSize="14px" data-cy="go-back">
Back <Flex>
<Image
sx={{ width: '10px', marginRight: '4px', rotate: '90deg' }}
src={ArrowIcon}
/>
<Text>Back</Text>
</Flex>
</Button> </Button>
</Link> </Link>
{/* Check if pin should be moderated */} {/* Check if pin should be moderated */}
@@ -96,18 +104,20 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
</Link> </Link>
)} )}
</Flex> </Flex>
<Text auxiliary mt={3} mb={2}> <Box mt={3} mb={2}>
By{' '} <Text inline auxiliary>
<Link By{' '}
sx={{ <Link
textDecoration: 'underline', sx={{
color: 'inherit', textDecoration: 'underline',
}} color: 'inherit',
to={'/u/' + howto._createdBy} }}
> to={'/u/' + howto._createdBy}
{howto._createdBy} >
</Link>{' '} {howto._createdBy}
<Text inline> {this.dateCreatedByText(howto)}</Text> </Link>{' '}
| Published on {this.dateCreatedByText(howto)}
</Text>
<Text auxiliary sx={{ color: '#b7b5b5 !important' }} mt={1} mb={2}> <Text auxiliary sx={{ color: '#b7b5b5 !important' }} mt={1} mb={2}>
{this.dateLastEditText(howto)} {this.dateLastEditText(howto)}
</Text> </Text>
@@ -117,18 +127,21 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
<Text preLine paragraph> <Text preLine paragraph>
{howto.description} {howto.description}
</Text> </Text>
</Text> </Box>
<Flex mt={4} mb={2}> <Flex mt="4">
<ElWithBeforeIcon IconUrl={StepsIcon} height="15px"> <Flex mr="4" flexDirection={iconFlexDirection}>
<Image src={StepsIcon} height="1em" mr="2" mb="2" />
{howto.steps.length} steps {howto.steps.length} steps
</ElWithBeforeIcon> </Flex>
<ElWithBeforeIcon IconUrl={TimeNeeded}> <Flex mr="4" flexDirection={iconFlexDirection}>
<Image src={TimeNeeded} height="1em" mr="2" mb="2" />
{howto.time} {howto.time}
</ElWithBeforeIcon> </Flex>
<ElWithBeforeIcon IconUrl={DifficultyLevel}> <Flex mr="4" flexDirection={iconFlexDirection}>
<Image src={DifficultyLevel} height="1em" mr="2" mb="2" />
{howto.difficulty_level} {howto.difficulty_level}
</ElWithBeforeIcon> </Flex>
</Flex> </Flex>
<Flex mt={4}> <Flex mt={4}>
{howto.tags && {howto.tags &&
@@ -156,8 +169,8 @@ export default class HowtoDescription extends React.PureComponent<IProps, any> {
<Image <Image
sx={{ sx={{
objectFit: 'cover', objectFit: 'cover',
width: '100%', width: 'auto',
height: '450px', height: '100%',
}} }}
src={howto.cover_image.downloadUrl} src={howto.cover_image.downloadUrl}
alt="how-to cover" alt="how-to cover"

View File

@@ -1,8 +1,7 @@
import React from 'react' import React from 'react'
import { Image, Card, Flex } from 'rebass/styled-components' import { Image, Card, Flex, CardProps } from 'rebass/styled-components'
import { IUploadedFileMeta } from 'src/stores/storage' import { IUploadedFileMeta } from 'src/stores/storage'
import Lightbox from 'react-image-lightbox' import Lightbox from 'react-image-lightbox'
import Text from 'src/components/Text'
import styled from 'styled-components' import styled from 'styled-components'
interface IProps { interface IProps {
@@ -16,7 +15,7 @@ interface IState {
imgIndex: number imgIndex: number
} }
const ThumbCard = styled(Card)` const ThumbCard = styled<CardProps & React.ComponentProps<any>>(Card)`
padding: 5px; padding: 5px;
overflow: hidden; overflow: hidden;
transition: 0.2s ease-in-out; transition: 0.2s ease-in-out;

View File

@@ -57,7 +57,7 @@ export class HowtoList extends React.Component<any, IState> {
onChange={tags => this.props.howtoStore.updateSelectedTags(tags)} onChange={tags => this.props.howtoStore.updateSelectedTags(tags)}
category="how-to" category="how-to"
styleVariant="filter" styleVariant="filter"
placeholder="Filter How-tos by tag" placeholder="Filter by tags"
relevantTagsItems={filteredHowtos} relevantTagsItems={filteredHowtos}
/> />
</Flex> </Flex>

View File

@@ -4,7 +4,9 @@
width: 100%; width: 100%;
max-width: 307px; max-width: 307px;
margin-top: 5px; margin-top: 5px;
border: 2px solid #1b1b1b; border-width: 2px !important;
border-color: #1b1b1b !important;
border-style: solid !important;
border-radius: 5px; border-radius: 5px;
outline: none; outline: none;
font-size: 14px; font-size: 14px;
@@ -12,17 +14,53 @@
user-select: none !important; user-select: none !important;
} }
.multi-select .dropdown-heading { .multi-select .dropdown .dropdown-heading {
height: 40px !important; height: 40px !important;
} }
.multi-select .dropdown-heading-value { .multi-select .dropdown .dropdown-heading .dropdown-heading-value {
line-height: 42px !important; line-height: 42px !important;
} }
.dropdown-heading { .multi-select .dropdown .dropdown-heading {
border-style: unset !important; border-style: unset !important;
border-color: black transparent transparent !important; border-color: black transparent transparent !important;
} }
.dropdown-heading-dropdown-arrow span {
border-color: black transparent transparent !important; .multi-select .dropdown .dropdown-heading .dropdown-heading-dropdown-arrow span {
border: none !important;
height: 10px !important;
width: 10px !important;
display: block !important;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.78 12.9'><g id='Calque_1' data-name='Calque 1'><path d='M12.61,5.23C11.2,6.48,9.81,7.75,8.4,9q-.35-3.9-.32-7.81a1.25,1.25,0,0,0-2.5,0q0,3.81.3,7.61C4.59,7.7,3.32,6.54,2.13,5.31S-.76,5.92.36,7.07a61.87,61.87,0,0,0,6.06,5.5,1.35,1.35,0,0,0,.53.25,1.2,1.2,0,0,0,1.24-.25c2.07-1.85,4.12-3.73,6.19-5.57C15.58,5.92,13.81,4.16,12.61,5.23Z'/></g></svg>") no-repeat !important;
/* Fixes jumping of arrow- presumably auto-set by the Multiselect component */
top: 0px !important;
} }
.multi-select .dropdown-content{
border-width: 2px !important;
border-color: #1b1b1b !important;
border-style: solid !important;
margin-top: 10px !important;
padding-left: 2px !important;
padding-right: 2px !important;
border-radius: 4px !important;
}
.multi-select {
cursor: pointer !important;
}
.multi-select * {
cursor: pointer !important;
}
.multi-select .dropdown .dropdown-heading span.dropdown-heading-value span{
color: black !important;
}
.multi-select .dropdown .dropdown-heading span.dropdown-heading-value:hover{
border-width: 1px !important;
border-color: rgb(131, 206, 235) !important;
border-style: solid !important;
}

View File

@@ -1,5 +1,4 @@
import React, { Component } from 'react' import React from 'react'
import styled from 'styled-components'
import MultiSelect from '@khanacademy/react-multi-select' import MultiSelect from '@khanacademy/react-multi-select'
import './GroupingFilter.css' import './GroupingFilter.css'
import ElWithBeforeIcon from 'src/components/ElWithBeforeIcon' import ElWithBeforeIcon from 'src/components/ElWithBeforeIcon'
@@ -35,8 +34,8 @@ const ItemRenderer = ({ checked, option, onClick }) => {
IconUrl={option.icon} IconUrl={option.icon}
ticked={checked} ticked={checked}
contain={true} contain={true}
width="30px" width="20px"
height="30px" height="20px"
> >
<h4 <h4
style={{ style={{
@@ -106,7 +105,7 @@ class GroupingFilterDesktop extends React.Component<IProps, IState> {
return values.length > 0 return values.length > 0
? null ? null
: entityType === 'place' : entityType === 'place'
? 'Workspaces' ? 'All Workspaces'
: 'Others' : 'Others'
}} }}
hasSelectAll={false} hasSelectAll={false}

View File

@@ -76,7 +76,7 @@ class GroupingFilterMobile extends React.Component<IProps, IState> {
return ( return (
<Flex flexDirection="column"> <Flex flexDirection="column">
<Text medium py="10px"> <Text medium py="10px">
{entityType === 'place' ? 'Workspaces' : 'Others'} {entityType === 'place' ? 'All Workspaces' : 'Others'}
</Text> </Text>
{options.map(filter => ( {options.map(filter => (
<Flex <Flex

View File

@@ -65,23 +65,20 @@ class Controls extends React.Component<IProps, IState> {
public render() { public render() {
const { availableFilters } = this.props const { availableFilters } = this.props
const { showFiltersMobile, filtersSelected } = this.state const { showFiltersMobile, filtersSelected } = this.state
const groupedFilters = availableFilters.reduce( const groupedFilters = availableFilters.reduce((accumulator, current) => {
(accumulator, current) => { const { grouping } = current
const { grouping } = current if (accumulator[grouping] === undefined) {
if (accumulator[grouping] === undefined) { accumulator[grouping] = []
accumulator[grouping] = [] }
} accumulator[grouping].push(current)
accumulator[grouping].push(current) return accumulator
return accumulator }, {} as Record<IPinGrouping, Array<IMapGrouping>>)
},
{} as Record<IPinGrouping, Array<IMapGrouping>>,
)
return ( return (
<MapFlexBar <MapFlexBar
data-cy="map-controls" data-cy="map-controls"
ml={['0', '0', '0', '50px']} ml={['0', '0', '0', '50px']}
py={[0, 1, 1]} py={[0, 1, 0]}
flexDirection={['column', 'column', 'column', 'row']} flexDirection={['column', 'column', 'column', 'row']}
alignItems={'center'} alignItems={'center'}
onClick={() => { onClick={() => {
@@ -101,7 +98,7 @@ class Controls extends React.Component<IProps, IState> {
onChange={(location: ILocation) => { onChange={(location: ILocation) => {
this.props.onLocationChange(location) this.props.onLocationChange(location)
}} }}
styleVariant="filter" styleVariant="mapinput"
/> />
</Box> </Box>
<Flex> <Flex>

View File

@@ -112,7 +112,7 @@ export class Popup extends React.Component<IProps> {
<Text tags mb={2}> <Text tags mb={2}>
{group ? group.displayName : pin.type} {group ? group.displayName : pin.type}
</Text> </Text>
<Link to={'u/' + name}> <Link to={'/u/' + name}>
<Text medium mb={1}> <Text medium mb={1}>
{name} {name}
</Text> </Text>

View File

@@ -1,3 +1,3 @@
.leaflet-tile { .leaflet-tile {
filter: saturate(0.3); filter: saturate(0.3);
} }

View File

@@ -47,6 +47,7 @@ export class ProfileLinkField extends Component<IProps, IState> {
// TODO - we might want to add more formatting for cases where, // TODO - we might want to add more formatting for cases where,
// e.g. only a username is given for a bazar link // e.g. only a username is given for a bazar link
public formatLink(link: string) { public formatLink(link: string) {
link = link && link.toLowerCase()
switch (this.state.linkType) { switch (this.state.linkType) {
case 'forum': case 'forum':
return ensureExternalUrl(link) return ensureExternalUrl(link)

View File

@@ -31,7 +31,7 @@ const rewriteUrl = (link: IProps['link']) => {
const ProfileLinkIcon = (link: IProps['link']) => { const ProfileLinkIcon = (link: IProps['link']) => {
const { label } = link const { label } = link
const icons: { [key in IProps['link']['label']]: JSX.Element } = { const icons: { [key in IProps['link']['label']]: JSX.Element | string } = {
forum: IconForum, forum: IconForum,
website: IconWebsite, website: IconWebsite,
email: IconEmail, email: IconEmail,

View File

@@ -392,9 +392,6 @@ export class UserPage extends React.Component<
</UserCategory> </UserCategory>
<Flex alignItems="center"> <Flex alignItems="center">
<Heading medium bold color={'black'} my={3} mr={2}>
{user.displayName}
</Heading>
{user.location ? ( {user.location ? (
<FlagIconEvents code={user.location.countryCode} /> <FlagIconEvents code={user.location.countryCode} />
) : ( ) : (
@@ -402,6 +399,9 @@ export class UserPage extends React.Component<
<FlagIconEvents code={user.country.toLowerCase()} /> <FlagIconEvents code={user.country.toLowerCase()} />
) )
)} )}
<Heading medium bold color={'black'} my={3} ml={2}>
{user.displayName}
</Heading>
</Flex> </Flex>
{user.about && ( {user.about && (
<Text <Text

View File

@@ -61,7 +61,7 @@ export default class Profile extends React.Component<IProps, IState> {
/> />
))} ))}
<MenuMobileLink <MenuMobileLink
path={location.pathname} path={window.location.pathname}
content={'Log out'} content={'Log out'}
style={{ color: theme.colors.silver }} style={{ color: theme.colors.silver }}
onClick={() => this.injected.userStore.logout()} onClick={() => this.injected.userStore.logout()}

View File

@@ -6,7 +6,7 @@ import { display, DisplayProps } from 'styled-system'
import { observer, inject } from 'mobx-react' import { observer, inject } from 'mobx-react'
import { MobileMenuStore } from 'src/stores/MobileMenu/mobilemenu.store' import { MobileMenuStore } from 'src/stores/MobileMenu/mobilemenu.store'
const ButtonSign = styled(Button)<DisplayProps>` const ButtonSign = styled(Button as any)<DisplayProps>`
${display}; ${display};
cursor: pointer; cursor: pointer;
` `

View File

@@ -11,7 +11,7 @@ interface ILayoutProps {
type IProps = FlexProps & ILayoutProps type IProps = FlexProps & ILayoutProps
const Main = (props: IProps) => ( const Main = (props: IProps) => (
<Flex {...props} flexDirection="column"> <Flex {...(props as any)} flexDirection="column">
<Box <Box
width="100%" width="100%"
className="main-container" className="main-container"

View File

@@ -82,8 +82,8 @@ export class LoginForm extends React.Component<IProps, IState> {
// track change internally for validation and emit to parent for processing // track change internally for validation and emit to parent for processing
handleChange(e: React.FormEvent<any>) { handleChange(e: React.FormEvent<any>) {
const nextValues = { ...this.state.formValues } const nextValues = { ...this.state.formValues }
;(nextValues[e.currentTarget.id] = e.currentTarget.value), nextValues[e.currentTarget.id] = e.currentTarget.value
this.setState({ formValues: nextValues }) this.setState({ formValues: nextValues })
this.props.onChange(e) this.props.onChange(e)
} }

View File

@@ -76,8 +76,8 @@ export class LoginComponent extends React.Component<IProps, IState> {
// (e.g. to retain the email typed when moving to forgot pw) // (e.g. to retain the email typed when moving to forgot pw)
public handleChange = (e: React.FormEvent<any>) => { public handleChange = (e: React.FormEvent<any>) => {
const nextValues = { ...this.state.formValues } const nextValues = { ...this.state.formValues }
;(nextValues[e.currentTarget.id] = e.currentTarget.value), nextValues[e.currentTarget.id] = e.currentTarget.value
this.setState({ formValues: nextValues }) this.setState({ formValues: nextValues })
} }
public render() { public render() {

View File

@@ -42,8 +42,8 @@ export class ResetPWForm extends React.Component<IProps, IState> {
// track change internally for validation and emit to parent for processing // track change internally for validation and emit to parent for processing
handleChange(e: React.FormEvent<any>) { handleChange(e: React.FormEvent<any>) {
const nextValues = { ...this.state.formValues } const nextValues = { ...this.state.formValues }
;(nextValues[e.currentTarget.id] = e.currentTarget.value), nextValues[e.currentTarget.id] = e.currentTarget.value
this.setState({ formValues: nextValues }) this.setState({ formValues: nextValues })
this.props.onChange(e) this.props.onChange(e)
} }

View File

@@ -67,8 +67,8 @@ export class SignUpForm extends React.Component<IProps, IState> {
// track change internally for validation and emit to parent for processing // track change internally for validation and emit to parent for processing
handleChange(e: React.FormEvent<any>) { handleChange(e: React.FormEvent<any>) {
const nextValues = { ...this.state.formValues } const nextValues = { ...this.state.formValues }
;(nextValues[e.currentTarget.id] = e.currentTarget.value), nextValues[e.currentTarget.id] = e.currentTarget.value
this.setState({ formValues: nextValues }) this.setState({ formValues: nextValues })
this.props.onChange(e) this.props.onChange(e)
} }

View File

@@ -45,7 +45,7 @@ export class SWUpdateNotification extends React.Component<IProps, IState> {
key="reload" key="reload"
color="secondary" color="secondary"
size="small" size="small"
onClick={() => location.reload()} onClick={() => window.location.reload()}
> >
Reload Reload
</Button>, </Button>,

View File

@@ -1,4 +1,3 @@
/* tslint:disable:no-eval */
import * as React from 'react' import * as React from 'react'
import { Switch, Route, BrowserRouter, Redirect } from 'react-router-dom' import { Switch, Route, BrowserRouter, Redirect } from 'react-router-dom'
import GoogleAnalytics from 'src/components/GoogleAnalytics' import GoogleAnalytics from 'src/components/GoogleAnalytics'
@@ -69,7 +68,7 @@ export class Routes extends React.Component<any, IState> {
<Route component={NotFoundPage} /> <Route component={NotFoundPage} />
</Switch> </Switch>
<Switch> <Switch>
<Route exact path="/" render={() => <Redirect to="/how-to" />} /> <Route exact path="/" render={() => <Redirect to="/academy" />} />
</Switch> </Switch>
</ScrollToTop> </ScrollToTop>
</BrowserRouter> </BrowserRouter>

View File

@@ -49,7 +49,11 @@ export class PrivacyPolicy extends React.PureComponent {
</p> </p>
<ul> <ul>
<li> <li>
<a href="https://policies.google.com/privacy" target="_blank"> <a
href="https://policies.google.com/privacy"
target="_blank"
rel="noopener noreferrer"
>
Google Analytics Google Analytics
</a> </a>
</li> </li>

View File

@@ -14,7 +14,7 @@ export class TermsPolicy extends React.PureComponent<any> {
<a <a
href="https://github.com/ONEARMY/community-platform/blob/master/LICENSE" href="https://github.com/ONEARMY/community-platform/blob/master/LICENSE"
target="_blank" target="_blank"
rel="nofollow" rel="noopener noreferrer"
> >
GitHub GitHub
</a> </a>
@@ -109,6 +109,7 @@ export class TermsPolicy extends React.PureComponent<any> {
<a <a
href="https://app-privacy-policy-generator.firebaseapp.com/" href="https://app-privacy-policy-generator.firebaseapp.com/"
target="_blank" target="_blank"
rel="noopener noreferrer"
> >
platform Privacy Policy Generator platform Privacy Policy Generator
</a> </a>

1
src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -1,4 +1,3 @@
// tslint:disable:no-console
// In production, we register a service worker to serve assets from local cache. // In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives // This lets the app load faster on subsequent visits in production, and gives
@@ -21,6 +20,7 @@ const isLocalhost = Boolean(
const register = (onUpdateCallback?: () => any) => { const register = (onUpdateCallback?: () => any) => {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
console.log('public url', process.env.PUBLIC_URL)
const publicUrl = new URL( const publicUrl = new URL(
process.env.PUBLIC_URL!, process.env.PUBLIC_URL!,
window.location.toString(), window.location.toString(),

5
src/setupTests.ts Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'

View File

@@ -15,7 +15,6 @@ import { MAP_GROUPINGS } from './maps.groupings'
import { generatePins, generatePinDetails } from 'src/mocks/maps.mock' import { generatePins, generatePinDetails } from 'src/mocks/maps.mock'
import { IUserPP } from 'src/models/user_pp.models' import { IUserPP } from 'src/models/user_pp.models'
import { IUploadedFileMeta } from '../storage' import { IUploadedFileMeta } from '../storage'
import { IUser } from 'src/models/user.models'
import { import {
hasAdminRights, hasAdminRights,
needsModeration, needsModeration,
@@ -212,7 +211,7 @@ export class MapsStore extends ModuleStore {
profilePicUrl: '', profilePicUrl: '',
shortDescription: '', shortDescription: '',
name: username, name: username,
profileUrl: `${location.origin}/u/${username}`, profileUrl: `${window.location.origin}/u/${username}`,
} }
} }
const avatar = getUserAvatar(username) const avatar = getUserAvatar(username)
@@ -227,7 +226,7 @@ export class MapsStore extends ModuleStore {
profilePicUrl: avatar, profilePicUrl: avatar,
shortDescription: u.mapPinDescription ? u.mapPinDescription : '', shortDescription: u.mapPinDescription ? u.mapPinDescription : '',
name: u.userName, name: u.userName,
profileUrl: `${location.origin}/u/${u.userName}`, profileUrl: `${window.location.origin}/u/${u.userName}`,
} }
} }
@action @action

View File

@@ -99,6 +99,7 @@ export class DexieClient implements AbstractDBClient {
***********************************************************************/ ***********************************************************************/
private _init() { private _init() {
const { location } = window
this._dbInit(DB_CACHE_NUMBER, DEXIE_SCHEMA) this._dbInit(DB_CACHE_NUMBER, DEXIE_SCHEMA)
// test open db, catch errors for upgrade version not defined or // test open db, catch errors for upgrade version not defined or
// idb not supported // idb not supported

View File

@@ -1,5 +1,3 @@
// tslint:disable max-classes-per-file
// tslint:disable variable-name
import { DexieClient } from './clients/dexie' import { DexieClient } from './clients/dexie'
import { FirestoreClient } from './clients/firestore' import { FirestoreClient } from './clients/firestore'
import { RealtimeDBClient } from './clients/rtdb' import { RealtimeDBClient } from './clients/rtdb'
@@ -55,7 +53,7 @@ export class DatabaseV2 implements AbstractDatabase {
*/ */
private _getDefaultClients = (): DBClients => { private _getDefaultClients = (): DBClients => {
const serverDB = new FirestoreClient() const serverDB = new FirestoreClient()
const cacheDB = location.search.includes('no-cache') const cacheDB = window.location.search.includes('no-cache')
? serverDB ? serverDB
: new DexieClient() : new DexieClient()
return { return {

View File

@@ -1,4 +1,3 @@
// tslint:disable interface-name max-classes-per-file
/** /**
* The summary section should be brief. On a documentation web site, * The summary section should be brief. On a documentation web site,
* it will be shown on a page that lists summaries for many different * it will be shown on a page that lists summaries for many different

View File

@@ -35,6 +35,7 @@ export type ButtonVariants =
| 'disabled' | 'disabled'
| 'dark' | 'dark'
| 'light' | 'light'
| 'subtle'
const space = [ const space = [
0, 0,
@@ -176,6 +177,25 @@ const buttons = {
}, },
borderRadius: radii[1] + 'px', borderRadius: radii[1] + 'px',
}, },
subtle: {
fontFamily: '"Varela Round", Arial, sans-serif',
border: 'none',
color: colors.black,
display: 'flex',
bg: colors.softblue,
transition: '.2s ease-in-out',
'&:hover': {
bg: colors.white,
cursor: 'pointer',
},
'&[disabled]': {
opacity: 0.5,
},
'&[disabled]:hover': {
bg: colors.softblue,
},
borderRadius: radii[1] + 'px',
},
} }
export default { export default {

View File

@@ -1,3 +0,0 @@
{
"extends": "../tsconfig.json"
}

View File

@@ -36,7 +36,6 @@ const newerThan = (chosenDate: dateType, compareToDate: dateType) => {
***********************************************************************/ ***********************************************************************/
export default { olderThan, newerThan } export default { olderThan, newerThan }
// tslint:disable variable-name
/************************************************************************ /************************************************************************
* Helper Methods * Helper Methods
***********************************************************************/ ***********************************************************************/

View File

@@ -28,4 +28,4 @@ export const functions = firebase.functions()
export const EmailAuthProvider = firebase.auth.EmailAuthProvider export const EmailAuthProvider = firebase.auth.EmailAuthProvider
// want to also expose the default firebase user // want to also expose the default firebase user
export interface IFirebaseUser extends firebase.User {} export type IFirebaseUser = firebase.User

View File

@@ -124,3 +124,6 @@ export const isAllowToPin = (pin: IMapPin, user?: IUser) => {
interface IEditableDoc extends DBDoc { interface IEditableDoc extends DBDoc {
_createdBy: string _createdBy: string
} }
// Convert theme em string to px number
export const emStringToPx = width => Number(width.replace('em', '')) * 16

View File

@@ -3,7 +3,7 @@
"target": "es5", "target": "es5",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"baseUrl": "./", "baseUrl": ".",
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
@@ -13,14 +13,13 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react",
"noImplicitAny": false, "noImplicitAny": false,
"strictPropertyInitialization": false, "strictPropertyInitialization": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"typeRoots": ["./node_modules/@types", "./types"], "typeRoots": ["./node_modules/@types", "./types"],
"composite": true,
"declaration": true, "declaration": true,
"outDir": "./lib" "noEmit": true
}, },
"include": ["src/**/*", "types"], "include": ["src/**/*", "types"],
"exclude": [ "exclude": [
@@ -31,8 +30,6 @@
"webpack", "webpack",
"jest", "jest",
"src/setupTests.ts", "src/setupTests.ts",
"config/*.js",
"config/jest/*.js",
"functions-cron", "functions-cron",
"functions", "functions",
"src/utils/UserMigration", "src/utils/UserMigration",

10
tsconfig.shared.json Normal file
View File

@@ -0,0 +1,10 @@
{
"___note___": "This config simply serves to build type declarations to allow import from src into functions",
"extends": "./tsconfig.json",
"compilerOptions": {
"composite": true,
"noEmit": false,
"outDir": "./build/types",
"emitDeclarationOnly": true
}
}

View File

@@ -1,30 +0,0 @@
{
"extends": ["tslint-config-prettier"],
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js",
"*.json",
"**/*.json",
"src/assets/userReportInit.js"
]
},
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"no-console": false,
"jsx-no-lambda": false,
"jsx-boolean-value": false,
"no-empty": true,
"no-any": false,
"prefer-const": true,
"member-access": false,
"array-type": false,
"no-reference": false,
"member-ordering": false,
"no-empty-interface": false,
"variable-name": false
},
"defaultSeverity": "warning"
}

View File

@@ -1 +0,0 @@
declare module 'wordpress-hash-node'

Some files were not shown because too many files have changed in this diff Show More