Change build folder

This commit is contained in:
soupette 2019-04-24 14:38:21 +02:00
parent 5b8ee4a5d1
commit 23fe34a6c4
1053 changed files with 97 additions and 91278 deletions

6
.gitignore vendored
View File

@ -119,3 +119,9 @@ dist
############################
packages/strapi-generate-new/files/public/
############################
# Example app
############################
# *.cache

View File

@ -1,12 +0,0 @@
# Don't check auto-generated stuff into git
coverage
node_modules
manifest.json
plugins.json
stats.json
package-lock.json
# Cruft
.DS_Store
npm-debug.log
.idea

View File

@ -1,101 +0,0 @@
############################
# OS X
############################
.DS_Store
.AppleDouble
.LSOverride
Icon
.Spotlight-V100
.Trashes
._*
############################
# Linux
############################
*~
############################
# Windows
############################
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp
############################
# Packages
############################
*.7z
*.csv
*.dat
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.com
*.class
*.dll
*.exe
*.o
*.seed
*.so
*.swo
*.swp
*.swn
*.swm
*.out
*.pid
############################
# Logs and databases
############################
*.log
*.sql
############################
# Misc.
############################
*#
ssl
.idea
nbproject
############################
# Node.js
############################
lib-cov
lcov.info
pids
logs
results
node_modules
.node_history
############################
# Tests
############################
test
testApp
coverage

View File

@ -1,213 +0,0 @@
// /**
// *
// * app.js
// *
// * Entry point of the application
// */
import '@babel/polyfill';
import 'sanitize.css/sanitize.css';
// Third party css library needed
// Currently unable to bundle them.
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/liquibyte.css';
import 'codemirror/theme/xq-dark.css';
import 'codemirror/theme/3024-day.css';
import 'codemirror/theme/3024-night.css';
import 'codemirror/theme/blackboard.css';
import 'codemirror/theme/monokai.css';
import 'codemirror/theme/cobalt.css';
import 'react-select/dist/react-select.css';
import 'react-datetime/css/react-datetime.css';
import './styles/main.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { merge } from 'lodash';
import {
freezeApp,
pluginLoaded,
unfreezeApp,
updatePlugin,
getAppPluginsSucceeded,
} from './containers/App/actions';
import { showNotification } from './containers/NotificationProvider/actions';
import basename from './utils/basename';
import injectReducer from './utils/injectReducer';
import injectSaga from './utils/injectSaga';
// Import root component
import App from './containers/App';
// Import Language provider
import LanguageProvider from './containers/LanguageProvider';
import configureStore from './configureStore';
// Import i18n messages
import { translationMessages, languages } from './i18n';
// Create redux store with history
import history from './utils/history';
import plugins from './plugins';
const initialState = {};
const store = configureStore(initialState, history);
const { dispatch } = store;
const MOUNT_NODE =
document.getElementById('app') || document.createElement('div');
dispatch(getAppPluginsSucceeded(Object.keys(plugins)));
Object.keys(plugins).forEach(plugin => {
const currentPlugin = plugins[plugin];
const pluginTradsPrefixed = languages.reduce((acc, lang) => {
const currentLocale = currentPlugin.trads[lang];
if (currentLocale) {
const localeprefixedWithPluginId = Object.keys(currentLocale).reduce(
(acc2, current) => {
acc2[`${plugins[plugin].id}.${current}`] = currentLocale[current];
return acc2;
},
{},
);
acc[lang] = localeprefixedWithPluginId;
}
return acc;
}, {});
try {
merge(translationMessages, pluginTradsPrefixed);
dispatch(pluginLoaded(currentPlugin));
} catch (err) {
console.log({ err });
}
});
// TODO
const remoteURL = (() => {
if (window.location.port === '4000') {
return 'http://localhost:4000/admin';
}
// Relative URL (ex: /dashboard)
if (process.env.REMOTE_URL[0] === '/') {
return (window.location.origin + process.env.REMOTE_URL).replace(/\/$/, '');
}
return process.env.REMOTE_URL.replace(/\/$/, '');
})();
const displayNotification = (message, status) => {
dispatch(showNotification(message, status));
};
const lockApp = data => {
dispatch(freezeApp(data));
};
const unlockApp = () => {
dispatch(unfreezeApp());
};
window.strapi = Object.assign(window.strapi || {}, {
node: MODE || 'host',
remoteURL,
backendURL: BACKEND_URL,
notification: {
success: message => {
displayNotification(message, 'success');
},
warning: message => {
displayNotification(message, 'warning');
},
error: message => {
displayNotification(message, 'error');
},
info: message => {
displayNotification(message, 'info');
},
},
refresh: pluginId => ({
translationMessages: translationMessagesUpdated => {
render(merge({}, translationMessages, translationMessagesUpdated));
},
leftMenuSections: leftMenuSectionsUpdated => {
store.dispatch(
updatePlugin(pluginId, 'leftMenuSections', leftMenuSectionsUpdated),
);
},
}),
router: history,
languages,
currentLanguage:
window.localStorage.getItem('strapi-admin-language') ||
window.navigator.language ||
window.navigator.userLanguage ||
'en',
lockApp,
unlockApp,
injectReducer,
injectSaga,
store,
});
const render = messages => {
ReactDOM.render(
<Provider store={store}>
<LanguageProvider messages={messages}>
<BrowserRouter basename={basename}>
<App store={store} />
</BrowserRouter>
</LanguageProvider>
</Provider>,
MOUNT_NODE,
);
};
if (module.hot) {
module.hot.accept(['./i18n', './containers/App'], () => {
ReactDOM.unmountComponentAtNode(MOUNT_NODE);
render(translationMessages);
});
}
if (NODE_ENV !== 'test') {
// Chunked polyfill for browsers without Intl support
if (!window.Intl) {
new Promise(resolve => {
resolve(import('intl'));
})
.then(() =>
Promise.all([
import('intl/locale-data/jsonp/en.js'),
import('intl/locale-data/jsonp/de.js'),
]),
) // eslint-disable-line prettier/prettier
.then(() => render(translationMessages))
.catch(err => {
throw err;
});
} else {
render(translationMessages);
}
}
// @Pierre Burgy exporting dispatch for the notifications...
export { dispatch };
// TODO remove this for the new Cypress tests
if (window.Cypress) {
window.__store__ = Object.assign(window.__store__ || {}, { store });
}

View File

@ -1 +0,0 @@
<svg width="28" height="26" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M8.453 12.772c-1.54.047-2.799.656-3.778 1.824h-1.91c-.779 0-1.435-.192-1.967-.577C.266 13.634 0 13.071 0 12.33c0-3.354.59-5.032 1.768-5.032.057 0 .263.1.62.3.356.2.82.401 1.39.605.57.205 1.135.307 1.696.307.636 0 1.268-.11 1.896-.328a7.106 7.106 0 0 0-.072.94c0 1.322.385 2.538 1.155 3.65zm15.266 9.08c0 1.14-.347 2.04-1.04 2.701-.694.66-1.616.99-2.766.99H7.455c-1.15 0-2.072-.33-2.765-.99-.694-.66-1.04-1.56-1.04-2.701 0-.504.016-.995.049-1.475.033-.48.1-.998.2-1.554s.225-1.072.377-1.547c.152-.475.357-.938.613-1.39.257-.45.551-.836.884-1.154a3.718 3.718 0 0 1 1.219-.763 4.28 4.28 0 0 1 1.59-.285c.094 0 .298.102.612.307.314.204.66.432 1.04.684.38.252.89.48 1.526.684a6.264 6.264 0 0 0 1.924.307c.646 0 1.288-.103 1.925-.307.636-.204 1.145-.432 1.525-.684.38-.252.727-.48 1.04-.684.314-.205.518-.307.613-.307.58 0 1.11.095 1.59.285.48.19.886.445 1.218.763.333.318.628.703.884 1.155.257.45.461.914.613 1.39.152.474.278.99.378 1.546.1.556.166 1.074.2 1.554.033.48.05.971.05 1.475zm3.65-9.522c0 .741-.267 1.304-.799 1.69-.532.384-1.188.576-1.967.576h-1.91c-.979-1.168-2.238-1.777-3.777-1.824.77-1.112 1.154-2.328 1.154-3.65 0-.275-.024-.588-.071-.94.627.219 1.259.328 1.896.328.56 0 1.126-.102 1.696-.307a9.374 9.374 0 0 0 1.39-.605c.356-.2.563-.3.62-.3 1.178 0 1.767 1.678 1.767 5.032z" fill="#553D38"/><path d="M9.123 3.65a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.068 3.516 3.516 0 0 1-2.58-1.069 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 5.474 0C6.48 0 7.34.356 8.054 1.07a3.516 3.516 0 0 1 1.069 2.58zm10.035 5.473c0 1.51-.535 2.8-1.604 3.87-1.069 1.069-2.359 1.603-3.87 1.603-1.51 0-2.8-.534-3.87-1.603-1.069-1.07-1.603-2.36-1.603-3.87 0-1.511.534-2.801 1.603-3.87 1.07-1.07 2.36-1.604 3.87-1.604 1.511 0 2.801.535 3.87 1.604 1.07 1.069 1.604 2.359 1.604 3.87zm6.386-5.474a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.07 3.516 3.516 0 0 1-2.58-1.07 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 21.895 0c1.007 0 1.867.356 2.58 1.07a3.516 3.516 0 0 1 1.069 2.58z" fill="#EBBF8E"/><path d="M12.496 19.991h2.393v-.897c0-.33-.117-.612-.35-.846a1.153 1.153 0 0 0-.847-.35c-.33 0-.612.116-.846.35-.233.234-.35.516-.35.846v.897zm3.889.45v2.691c0 .125-.044.231-.131.318a.433.433 0 0 1-.318.131h-4.487a.433.433 0 0 1-.318-.13.433.433 0 0 1-.131-.319V20.44c0-.124.044-.23.13-.318a.433.433 0 0 1 .319-.13h.15v-.898c0-.573.205-1.066.616-1.477A2.015 2.015 0 0 1 13.692 17c.574 0 1.066.206 1.477.617.412.411.617.904.617 1.477v.897h.15c.125 0 .23.044.318.131.087.088.13.194.13.318z" fill="#FFF"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1 +0,0 @@
<svg width="35" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M1.148 20.03c.567-7.141 2.974-7.675 7.222-1.604 6.372 9.107 7.533-.667 9.19-2.017" stroke="#FFD2B6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g fill-rule="nonzero"><path d="M21.313 16.48l1.081-1.082-2.792-2.792-1.081 1.081v1.271h1.52v1.521h1.272zm6.214-11.027c0-.174-.087-.26-.262-.26a.275.275 0 0 0-.202.082l-6.44 6.44a.275.275 0 0 0-.082.202c0 .174.087.261.261.261.08 0 .147-.028.202-.083l6.44-6.44a.275.275 0 0 0 .083-.202zm-.642-2.28l4.943 4.942L21.943 18H17v-4.943l9.885-9.885zM35 4.312c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#181D29"/><path d="M35 4.313c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#FF84B3"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 980 B

View File

@ -1 +0,0 @@
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M8.604.607c.317.623.75 1.155 1.298 1.597.549.443 1.16.754 1.835.934l.012.873c.032 1.745-.494 3.166-1.579 4.264-1.084 1.098-2.5 1.647-4.247 1.647-1 0-1.885-.19-2.657-.571a4.852 4.852 0 0 1-1.858-1.567 7.325 7.325 0 0 1-1.055-2.25A9.927 9.927 0 0 1 0 2.832l.5.369c.276.205.528.387.755.547.228.16.468.309.72.448.251.14.438.21.56.21.333 0 .557-.152.67-.455a6.33 6.33 0 0 1 .701-1.383c.264-.381.547-.692.847-.934.3-.242.658-.436 1.073-.584A6.547 6.547 0 0 1 7.08.736c.422-.062.93-.105 1.523-.13z" id="a"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 12)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#EABE8D" fill-rule="nonzero" xlink:href="#a"/><path d="M10.18.264c4.086 6.335 2.315 9.584-5.314 9.747-7.629.163-6.185-1.797 4.331-5.88l.982-3.867z" fill="#DB1818" mask="url(#b)"/><path d="M10.564.16C11.422 6.26 8.9 9.507 2.998 9.904c-5.902.397-4.484-1.392 4.255-5.367L10.563.16z" fill="#F1D520" mask="url(#b)"/><path d="M10.196-1.556C9.168 5.281 6.272 8.728 1.507 8.786c-4.764.059-3.209-1.83 4.668-5.663l4.021-4.679z" fill="#32B167" mask="url(#b)"/><path d="M9.506-4.516C9.417 2.713 6.99 6.357 2.226 6.414-2.539 6.474-.784 4.262 7.49-.22l2.016-4.295z" fill="#1279EA" mask="url(#b)"/><path d="M9.449-4.392C8.72 1.871 5.974 5.032 1.209 5.09c-4.764.058-3.398-1.712 4.099-5.311l4.14-4.17z" fill="#4C2DCE" mask="url(#b)"/></g><path d="M19.88 0c.564 0 1.058.186 1.482.56.424.372.635.84.635 1.401 0 .505-.181 1.111-.544 1.817-2.679 5.045-4.555 8.06-5.628 9.047-.783.73-1.662 1.095-2.638 1.095-1.017 0-1.89-.37-2.62-1.113-.73-.742-1.096-1.622-1.096-2.64 0-1.027.371-1.877 1.113-2.551L18.306.65c.476-.433 1-.65 1.573-.65z" fill-rule="nonzero" fill="#553D38"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1 +0,0 @@
<svg width="54" height="34" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path d="M11.392 30.3a1.797 1.797 0 0 0-.22-1.409 1.797 1.797 0 0 0-1.141-.857 1.797 1.797 0 0 0-1.41.22c-.449.27-.734.65-.857 1.142-.122.492-.049.962.22 1.41.27.449.65.734 1.142.857.491.122.961.049 1.41-.22.448-.27.734-.65.856-1.142zm21.224-7.353L8.464 37.459c-.874.525-1.812.663-2.813.413-.983-.245-1.756-.809-2.318-1.692L1.09 32.37c-.548-.86-.695-1.8-.44-2.82.249-1.002.822-1.772 1.72-2.311l24.117-14.492a14.885 14.885 0 0 0 2.02 5.728 14.885 14.885 0 0 0 4.108 4.472z" fill="#181D29"/><path d="M53.663 15.097c-.184.737-.65 1.684-1.401 2.842-1.52 2.31-3.587 3.978-6.2 5.003-2.615 1.024-5.254 1.204-7.918.54-3.497-.872-6.177-2.86-8.043-5.965-1.865-3.105-2.362-6.405-1.49-9.901.871-3.496 2.86-6.177 5.964-8.043 3.105-1.865 6.405-2.362 9.901-1.49 1.096.273 2.205.715 3.328 1.326 1.122.611 2.028 1.304 2.718 2.078.251.283.336.586.256.907-.08.321-.297.548-.651.68l-9.5 2.72-1.584 6.35 4.715 4.397c.109-.033.97-.305 2.582-.816 1.613-.511 3.084-.957 4.414-1.339 1.33-.38 2.08-.55 2.25-.508.283.071.481.22.595.45.113.229.135.485.064.769z" fill="#A1A1A1"/></g><path stroke="#C6C6C6" fill="#D8D8D8" d="M46.521 5.425l3.657 3.41-1.125 4.872-4.781 1.461-3.657-3.41 1.125-4.871z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
<title>Check</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Home-view" transform="translate(-1086.000000, -34.000000)">
<g id="Notification-2---Success" transform="translate(1066.000000, 14.000000)">
<g id="Check" transform="translate(20.000000, 20.000000)">
<g id="check" transform="translate(5.500000, 7.000000)" fill-rule="nonzero" fill="#27B70F">
<path d="M9.34239514,1.3983456 C9.34239514,1.55907497 9.28613986,1.69569495 9.1736293,1.80820551 L4.80982666,6.17200815 L3.99010683,6.99172798 C3.87759626,7.10423854 3.74097629,7.16049383 3.58024691,7.16049383 C3.41951753,7.16049383 3.28289756,7.10423854 3.170387,6.99172798 L2.35066717,6.17200815 L0.168765848,3.99010683 C0.0562552826,3.87759626 0,3.74097629 0,3.58024691 C0,3.41951753 0.0562552826,3.28289756 0.168765848,3.170387 L0.98848568,2.35066717 C1.10099625,2.2381566 1.23761622,2.18190132 1.3983456,2.18190132 C1.55907497,2.18190132 1.69569495,2.2381566 1.80820551,2.35066717 L3.58024691,4.12873592 L7.53418963,0.168765848 C7.6467002,0.0562552826 7.78332017,0 7.94404955,0 C8.10477893,0 8.2413989,0.0562552826 8.35390947,0.168765848 L9.1736293,0.98848568 C9.28613986,1.10099625 9.34239514,1.23761622 9.34239514,1.3983456 Z" id="Shape"></path>
</g>
<rect id="Rectangle-2" stroke="#27B70F" x="0.5" y="0.5" width="19" height="19" rx="9.5"></rect>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1 +0,0 @@
<svg width="24" height="17" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-23 -9)" fill="#4B515A" fill-rule="evenodd" font-size="24" font-family="AppleColorEmoji, Apple Color Emoji"><tspan x="23" y="28">👕</tspan></text></svg>

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,25 +0,0 @@
/**
* CTAWrapper
*
*/
import React from 'react';
import PropTypes from 'prop-types';
function CTAWrapper({ children }) {
return <div style={style}>{children}</div>;
}
const style = {
position: 'fixed',
top: '0',
right: '0',
display: 'flex',
zIndex: '1050',
};
CTAWrapper.propTypes = {
children: PropTypes.node.isRequired,
};
export default CTAWrapper;

View File

@ -1,27 +0,0 @@
/*
*
* DownloadInfo
*
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import Icon from '../../assets/icons/icon_success.svg';
import styles from './styles.scss';
function DownloadInfo() {
return (
<div className={styles.wrapper}>
<div className={styles.content}>
<img src={Icon} alt="info" />
<div>
<FormattedMessage id="app.components.DownloadInfo.download" />
<br />
<FormattedMessage id="app.components.DownloadInfo.text" />
</div>
</div>
</div>
);
}
export default DownloadInfo;

View File

@ -1,34 +0,0 @@
.wrapper {
width: 372px;
height: 159px;
border-radius: 2px;
background-color: #fff;
}
.content {
padding-top: 3rem;
// color: #F64D0A;
text-align: center;
font-family: Lato;
font-size: 1.3rem;
> img {
width: 2.5rem;
margin-bottom: 1.5rem;
}
> div {
padding-top: 9px;
line-height: 18px;
> span:first-child {
color: #333740;
font-size: 16px;
font-weight: 600;
}
> span {
color: #787E8F;
font-size: 13px;
}
}
}

View File

@ -1,95 +0,0 @@
/*
* Copyright@React-FullStory (https://github.com/cereallarceny/react-fullstory)
*/
import React from 'react';
import PropTypes from 'prop-types';
const canUseDOM = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
export const getWindowFullStory = () => window[window['_fs_namespace']];
class FullStory extends React.Component {
constructor(props) {
super(props);
window['_fs_debug'] = false;
window['_fs_host'] = 'fullstory.com';
window['_fs_org'] = props.org;
window['_fs_namespace'] = 'FS';
(function(m,n,e,t,l,o,g,y) {
if (e in m) {
if(m.console && m.console.log) {
m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');
}
return;
}
g = m[e]= function(a,b,s) {
g.q ? g.q.push([a,b,s]) : g._api(a,b,s);
};
g.q=[];
o = n.createElement(t);
o.async = 1;
o.src = `https://${window._fs_host}/s/fs.js`;
y = n.getElementsByTagName(t)[0];
y.parentNode.insertBefore(o,y);
g.identify = function(i,v,s) {
g(l,{ uid:i },s);
if (v) {
g(l,v,s);
}
};
g.setUserVars = function(v,s) {
g(l,v,s);
};
g.event = function(i,v,s) {
g('event',{ n:i,p:v },s);
};
g.shutdown = function() {
g("rec",!1);
};
g.restart = function() {
g("rec",!0);
};
g.consent = function(a) {
g("consent",!arguments.length||a);
};
g.identifyAccount = function(i,v) {
o = 'account';
v = v||{};
v.acctId = i;
g(o,v);
};
g.clearUserCookie = function() {};
})(window, document, window['_fs_namespace'], 'script', 'user');
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
if (!canUseDOM || !getWindowFullStory()) return false;
getWindowFullStory().shutdown();
delete getWindowFullStory();
}
render() {
return false;
}
}
FullStory.propTypes = {
org: PropTypes.string.isRequired,
};
export default FullStory;

View File

@ -1,12 +0,0 @@
/**
*
* Header
*
*/
import React from 'react';
import styles from './styles.scss';
export default function Header() {
return <div className={styles.header} />;
}

View File

@ -1,15 +0,0 @@
// Import
@import "../../styles/variables/variables";
.header { /* stylelint-ignore */
width: 100%;
height: $header-height;
position: fixed;
z-index: 1040;
left: $left-menu-width;
box-shadow: 0 1px 2px 0 rgba(40, 42, 49, 0.16);
background-color: $white;
line-height: $header-height;
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,36 +0,0 @@
/**
*
* HomePageBlock
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import styles from './styles.scss';
function HomePageBlock({ children, className }) {
return (
<div
className={cn(
className,
styles.homePageBlock,
)}
>
{children}
</div>
);
}
HomePageBlock.defaultProps = {
children: '',
className: '',
};
HomePageBlock.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
export default HomePageBlock;

View File

@ -1,8 +0,0 @@
.homePageBlock {
width: 100%;
margin-bottom: 34px;
background: #ffffff;
padding: 20px 30px 30px 30px;
box-shadow: 0 2px 4px 0 #E3E9F3;
border-radius: 3px;
}

View File

@ -1,161 +0,0 @@
/*
*
* InstallPluginPopup
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { map } from 'lodash';
import cn from 'classnames';
import Official from '../Official';
// import StarsContainer from 'components/StarsContainer';
import styles from './styles.scss';
class InstallPluginPopup extends React.Component {
handleClick = () => {
this.props.history.push({ pathname: this.props.history.location.pathname });
if (!this.props.isAlreadyInstalled) {
this.context.downloadPlugin(this.props.plugin.id);
}
}
toggle = () => {
this.props.history.push({
pathname: this.props.history.location.pathname,
});
}
navLinks = [
{
content: 'app.components.InstallPluginPopup.navLink.description',
name: 'description',
},
{
content: 'app.components.InstallPluginPopup.navLink.screenshots',
name: 'screenshots',
},
{
content: 'app.components.InstallPluginPopup.navLink.avis',
name: 'avis',
},
{
content: 'app.components.InstallPluginPopup.navLink.faq',
name: 'faq',
},
{
content: 'app.components.InstallPluginPopup.navLink.changelog',
name: 'changelog',
},
];
render() {
const descriptions = {
short: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.short} /> : this.props.plugin.description.short,
long: this.props.plugin.id === 'support-us' ? <FormattedMessage id={this.props.plugin.description.long || this.props.plugin.description.short} /> : this.props.plugin.description.long || this.props.plugin.description.short,
};
const buttonName = this.props.isAlreadyInstalled ? 'app.components.PluginCard.Button.label.install' : 'app.components.InstallPluginPopup.downloads';
return (
<Modal isOpen={this.props.isOpen} toggle={this.toggle} className={styles.modalPosition}>
<ModalHeader toggle={this.toggle} className={styles.modalHeader} />
<ModalBody className={styles.modalBody}>
<div className={styles.wrapper}>
<div className={styles.headerWrapper}>
<div className={styles.logo}><img src={`${this.props.plugin.logo}`} alt="icon" /></div>
<div className={styles.headerInfo}>
<div className={styles.name}>{this.props.plugin.name}</div>
<div className={styles.ratings}>
{/*}
<StarsContainer ratings={this.props.plugin.ratings} />
<div>
<span style={{ fontWeight: '600', color: '#333740', fontSize: '12px'}}>{this.props.plugin.ratings}</span>
<span style={{ fontWeight: '500', color: '#666666', fontSize: '11px' }}>/5</span>
</div>
*/}
<Official style={{ marginTop: '0' }} />
</div>
<div className={styles.headerDescription}>
{descriptions.short}
</div>
<div className={styles.headerButtonContainer}>
<div>
<i className={`fa fa-${this.props.plugin.isCompatible ? 'check' : 'times'}`} />
<FormattedMessage id={`app.components.PluginCard.compatible${this.props.plugin.id === 'support-us' ? 'Community' : ''}`} />
</div>
<div>
<div>
{/*}
<span style={{ fontWeight: '600' }}>+{this.props.plugin.downloads_nb}k&nbsp;</span><FormattedMessage id="app.components.InstallPluginPopup.downloads" />
*/}
</div>
<div className={styles.buttonWrapper} onClick={this.handleClick}>
<div>
<FormattedMessage id={buttonName} />
</div>
{/* Uncomment whebn prices are running}
<div>{this.props.plugin.price}&nbsp;</div>
*/}
</div>
</div>
</div>
</div>
</div>
</div>
<div className={styles.navContainer}>
{map(this.navLinks, link => {
const isActive = this.props.history.location.hash.split('::')[1] === link.name;
return (
<div
key={link.name}
className={cn(isActive ? styles.navLink : '', link.name !== 'description' ? styles.notAllowed : '')}
onClick={() => {
if (link.name === 'description') {
this.props.history.push({ pathname: this.props.history.location.pathname, hash: `${this.props.plugin.id}::${link.name}` });
}
}}
style={isActive ? { paddingTop: '4px'} : { paddingTop: '6px' }}
>
<FormattedMessage id={link.content} />
</div>
);
})}
</div>
<div className={styles.pluginDescription}>
{descriptions.long}
</div>
</ModalBody>
</Modal>
);
}
}
InstallPluginPopup.contextTypes = {
downloadPlugin: PropTypes.func.isRequired,
};
InstallPluginPopup.defaultProps = {
description: {
short: 'app.Components.InstallPluginPopup.noDescription',
},
};
InstallPluginPopup.propTypes = {
description: PropTypes.shape({
long: PropTypes.string,
short: PropTypes.string,
}),
history: PropTypes.object.isRequired,
isAlreadyInstalled: PropTypes.bool.isRequired,
isOpen: PropTypes.bool.isRequired,
plugin: PropTypes.object.isRequired,
};
export default InstallPluginPopup;

View File

@ -1,199 +0,0 @@
.buttonWrapper {
display: flex;
justify-content: space-between;
min-width: 136px;
height: 30px;
margin-left: 20px;
border-radius: 0.3rem;
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
color: white;
font-size: 13px;
font-style: normal;
font-weight: 600;
cursor: pointer;
&:focus {
outline: 0;
}
> div {
text-align: center;
text-transform: capitalize;
&:first-child {
flex-grow: 2;
}
}
}
.headerButtonContainer {
display: flex;
justify-content: space-between;
padding-top: 13px;
line-height: 29px;
> div {
font-size: 13px;
font-style: italic;
}
> div:first-child {
> i {
margin-right: 10px;
}
color: #5A9E06;
}
> div:nth-child(2) {
display: flex;
> span:nth-child(2) {
font-style: normal;
}
}
}
.headerDescription {
height: 48px;
padding-top: 12px;
font-size: 13px;
}
.headerInfo {
flex-grow: 1;
padding-top: 7px;
padding-left: 30px;
}
.headerWrapper {
display: flex;
}
.logo {
flex-shrink: 0;
width: 144px;
height: 144px;
background: #FAFAFB;
border: 1px solid #F3F3F7;
border-radius: 3px;
background-size: contain;
line-height: 144px;
text-align: center;
> img {
min-width: 72px;
// max-height: 72px;
}
}
.modalHeader {
margin: 0 1.4rem !important;
padding: 1.4rem 0 0 0 !important;
border-bottom: 0 !important;
> button {
margin-right: -1rem !important;
color: #C3C5C8;
opacity: 1;
font-size: 1.8rem;
font-weight: 100;
z-index: 999;
&:hover, &:focus {
color: #C3C5C8;
opacity: 1;
outline: 0!important;
cursor: pointer;
}
> span {
display: none;
}
&:before {
-webkit-font-smoothing: antialiased;
content: '\F00d';
font-family: 'FontAwesome';
font-weight: 400;
font-size: 1.2rem;
margin-right: 10px;
}
}
}
.modalBody {
padding: 5px 0px !important;
}
.modalPosition {
> div {
border:none;
border-radius: 2px;
width: 74.5rem;
padding-left: 0;
padding-right: 0;
}
}
.name {
padding-bottom: 1px;
font-size: 16px;
font-weight: 700;
text-transform: uppercase;
}
.navContainer {
display: flex;
justify-content: space-between;
height: 36px;
margin-top: 38px;
padding: 0 30px;
background-color: #FAFAFB;
font-size: 13px;
font-weight: 600;
text-transform: capitalize;
> div {
flex-grow: 2;
text-align: center;
line-height: 23px;
cursor: pointer;
}
}
.navLink {
border-top: 2px solid #1C5DE7;
}
.notAllowed {
opacity: .3;
cursor: not-allowed !important;
}
.pluginDescription {
padding: 24px 30px;
font-size: 13px;
line-height: 18px;
}
.ratings {
display: flex;
line-height: 18px;
> div:last-child {
padding-top: 1px;
font-style: italic;
}
}
.starsContainer {
display: flex;
> div {
color: #EED348;
> i {
margin-right: 2px;
}
}
> div:last-child {
color: #B3B5B9;
}
}
.wrapper {
padding: 0 30px;
}

View File

@ -1,65 +0,0 @@
/**
*
* LeftMenuFooter
*
*/
import React from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { PropTypes } from 'prop-types';
import LeftMenuLink from '../LeftMenuLink';
import styles from './styles.scss';
import messages from './messages.json';
defineMessages(messages);
function LeftMenuFooter({ version, ...rest }) {
const staticLinks = [
{
icon: 'book',
label: 'documentation',
destination: 'https://strapi.io/documentation',
},
{
icon: 'question-circle',
label: 'help',
destination: 'https://strapi.io/help',
},
];
return (
<div className={styles.leftMenuFooter}>
<ul className={styles.list}>
{staticLinks.map(link => (
<LeftMenuLink
{...rest}
{...link}
destination={messages[link.label].id}
key={link.label}
/>
))}
</ul>
<div className={styles.poweredBy}>
<FormattedMessage {...messages.poweredBy} key="poweredBy" />
<a key="website" href="https://strapi.io" target="_blank">
Strapi
</a>
&nbsp;
<a
href={`https://github.com/strapi/strapi/releases/tag/v${version}`}
key="github"
target="_blank"
>
v{version}
</a>
</div>
</div>
);
}
LeftMenuFooter.propTypes = {
version: PropTypes.string.isRequired,
};
export default LeftMenuFooter;

View File

@ -1,14 +0,0 @@
{
"documentation": {
"id": "app.components.LeftMenuFooter.documentation",
"defaultMessage": "Documentation"
},
"help": {
"id": "app.components.LeftMenuFooter.help",
"defaultMessage": "Help"
},
"poweredBy": {
"id": "app.components.LeftMenuFooter.poweredBy",
"defaultMessage": "Proudly powered by "
}
}

View File

@ -1,30 +0,0 @@
// Import
@import "../../styles/variables/variables";
.leftMenuFooter { /* stylelint-disable */
position: absolute;
width: 100%;
background: $left-menu-bg;
bottom: 0;
}
.list {
list-style: none;
padding: 0;
margin-bottom: 0;
}
.poweredBy {
width: 100%;
bottom: 0;
height: 3rem;
padding-left: 15px;
padding-right: 15px;
line-height: 3rem;
background-color: rgba(255, 255, 255, .02);
font-size: 1rem;
font-weight: 400;
letter-spacing: 0.05rem;
vertical-align: middle;
color: $strapi-gray-light;
}

View File

@ -1,22 +0,0 @@
/**
*
* LeftMenuHeader
*
*/
import React from 'react';
import { Link } from 'react-router-dom';
import styles from './styles.scss';
function LeftMenuHeader() {
return (
<div className={styles.leftMenuHeader}>
<Link to="/" className={styles.leftMenuHeaderLink}>
<span className={styles.projectName} />
</Link>
</div>
);
}
export default LeftMenuHeader;

View File

@ -1,31 +0,0 @@
// Import
@import "../../styles/variables/variables";
.leftMenuHeader { /* stylelint-ignore */
background: $left-menu-link-hover;
height: $header-height;
background: linear-gradient(100deg , #1C5DE7, #1C91E7);
}
.projectName {
display: block;
height: 100%;
width: 100%;
text-align: center;
line-height: $header-height;
vertical-align: middle;
font-size: 2rem;
letter-spacing: 0.2rem;
color: $white;
background-image: url('../../assets/images/logo-strapi.png');
background-repeat: no-repeat;
background-position: center center;
background-size: auto 3rem;
}
.leftMenuHeaderLink {
&:hover {
text-decoration: none;
}
}

View File

@ -1,94 +0,0 @@
/**
*
* LeftMenuLink
*
*/
import React from 'react';
import { startsWith, upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import en from '../../translations/en.json';
import styles from './styles.scss';
function LeftMenuLink(props) {
const isLinkActive = startsWith(
props.location.pathname.replace('/admin', '').concat('/'),
props.destination.concat('/'),
);
const plugin =
props.source !== 'content-manager' && props.source !== '' ? (
<div className={styles.plugin}>
<span>{upperFirst(props.source.split('-').join(' '))}</span>
</div>
) : (
''
);
// Check if messageId exists in en locale to prevent warning messages
const content = en[props.label] ? (
<FormattedMessage
id={props.label}
defaultMessage="{label}"
values={{
label: `${props.label}`,
}}
className={styles.linkLabel}
/>
) : (
<span className={styles.linkLabel}>{props.label}</span>
);
// Icon.
const icon = <i className={`${styles.linkIcon} fa-${props.icon} fa`} />;
// Create external or internal link.
const link = props.destination.includes('http') ? (
<a
className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`}
href={props.destination}
target="_blank"
>
{icon}
{content}
</a>
) : (
<Link
className={`${styles.link} ${isLinkActive ? styles.linkActive : ''}`}
to={{
pathname: props.destination,
search: props.source ? `?source=${props.source}` : '',
}}
>
{icon}
{content}
</Link>
);
return (
<li className={styles.item}>
{link}
{plugin}
</li>
);
}
LeftMenuLink.propTypes = {
destination: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string,
}).isRequired,
source: PropTypes.string,
};
LeftMenuLink.defaultProps = {
source: '',
};
export default LeftMenuLink;

View File

@ -1,89 +0,0 @@
// Import
@import "../../styles/variables/variables";
.item {
position: relative;
overflow: hidden;
&:not(:first-child) {
margin-top: 0;
}
.plugin {
cursor: pointer;
position: absolute;
top: 10px; left: calc(100% - 4px);
display: inline-block;
width: auto;
height: 20px;
transition: right 1s ease-in-out;
span{
display: inline-block;
overflow: hidden;
width: auto;
height: 20px;
padding: 0 14px 0 10px;
color: #ffffff;
font-size: 12px;
line-height: 20px;
background: #0097f7;
border-radius: 3px;
transition: transform .3s ease-in-out;
white-space: pre;
&:hover{
transform: translateX(calc(-100% + 9px));
}
}
}
}
.link {
padding-top: .9rem;
padding-bottom: 0.2rem;
padding-left: 1.6rem;
min-height: 3.6rem;
border-left: 0.3rem solid transparent;
cursor: pointer;
color: $left-menu-link-color;
text-decoration: none;
display: block;
-webkit-font-smoothing: antialiased;
&:hover {
color: $white;
background: $left-menu-link-hover;
border-left: 0.3rem solid $strapi-blue;
text-decoration: none;
}
&:focus {
color: $white;
text-decoration: none;
}
&:visited {
color: $left-menu-link-color;
}
}
.linkActive {
color: $white !important;
border-left: 0.3rem solid $strapi-blue;
}
.linkIcon {
position: relative;
margin-right: 1.2rem;
font-size: 1.2rem;
width: 1.4rem;
height: 1.2rem;
padding-bottom: 0.2rem;
text-align: center;
}
.linkLabel {
display: inline-block;
padding-right: 1rem;
}

View File

@ -1,140 +0,0 @@
/**
*
* LeftMenuLinkContainer
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { get, snakeCase, isEmpty, map, sortBy } from 'lodash';
import LeftMenuLink from '../LeftMenuLink';
import styles from './styles.scss';
import messages from './messages.json';
function LeftMenuLinkContainer({ plugins, ...rest }) {
// Generate the list of sections
const pluginsSections = Object.keys(plugins).reduce((acc, current) => {
plugins[current].leftMenuSections.forEach((section = {}) => {
if (!isEmpty(section.links)) {
acc[snakeCase(section.name)] = {
name: section.name,
links: get(acc[snakeCase(section.name)], 'links', []).concat(
section.links.map(link => {
link.source = current;
link.plugin = !isEmpty(plugins[link.plugin])
? link.plugin
: plugins[current].id;
return link;
}),
),
};
}
});
return acc;
}, {});
const linkSections = Object.keys(pluginsSections).map((current, j) => {
const contentTypes = pluginsSections[current].links;
return (
<div key={j}>
<p className={styles.title}>{pluginsSections[current].name}</p>
<ul className={styles.list}>
{sortBy(contentTypes, 'label').map((link, i) => (
<LeftMenuLink
{...rest}
key={`${i}-${link.label}`}
icon={link.icon || 'caret-right'}
label={link.label}
destination={`/plugins/${link.plugin}/${link.destination}`}
source={link.source}
/>
))}
</ul>
</div>
);
});
// Check if the plugins list is empty or not and display plugins by name
const pluginsLinks = !isEmpty(plugins) ? (
map(sortBy(plugins, 'name'), plugin => {
if (plugin.id !== 'email' && plugin.id !== 'settings-manager') {
const basePath = `/plugins/${get(plugin, 'id')}`;
// NOTE: this should be dynamic
const destination =
plugin.id === 'content-manager'
? `${basePath}/ctm-configurations`
: basePath;
return (
<LeftMenuLink
{...rest}
key={get(plugin, 'id')}
icon={get(plugin, 'icon') || 'plug'}
label={get(plugin, 'name')}
destination={destination}
/>
);
}
})
) : (
<li key="emptyList" className={styles.noPluginsInstalled}>
<FormattedMessage {...messages.noPluginsInstalled} key="noPlugins" />.
</li>
);
const hasSettingsManager = get(plugins, 'settings-manager', null);
const staticLinks = [
{
icon: 'list',
label: messages.listPlugins.id,
destination: '/list-plugins',
},
{
icon: 'shopping-basket',
label: messages.installNewPlugin.id,
destination: '/marketplace',
},
];
return (
<div className={styles.leftMenuLinkContainer}>
{linkSections}
<div>
<p className={styles.title}>
<FormattedMessage {...messages.plugins} />
</p>
<ul className={styles.list}>{pluginsLinks}</ul>
</div>
<div>
<p className={styles.title}>
<FormattedMessage {...messages.general} />
</p>
<ul className={styles.list}>
{staticLinks.map(link => (
<LeftMenuLink {...rest} key={link.destination} {...link} />
))}
{hasSettingsManager && (
<LeftMenuLink
{...rest}
icon="gear"
label={messages.configuration.id}
destination="/plugins/settings-manager"
/>
)}
</ul>
</div>
</div>
);
}
LeftMenuLinkContainer.propTypes = {
plugins: PropTypes.object.isRequired,
};
export default LeftMenuLinkContainer;

View File

@ -1,26 +0,0 @@
{
"listPlugins": {
"id": "app.components.LeftMenuLinkContainer.listPlugins",
"defaultMessage": "Plugins"
},
"installNewPlugin": {
"id": "app.components.LeftMenuLinkContainer.installNewPlugin",
"defaultMessage": "Marketplace"
},
"configuration": {
"id": "app.components.LeftMenuLinkContainer.configuration",
"defaultMessage": "Configurations"
},
"plugins": {
"id": "app.components.LeftMenuLinkContainer.plugins",
"defaultMessage": "Plugins"
},
"general": {
"id": "app.components.LeftMenuLinkContainer.general",
"defaultMessage": "General"
},
"noPluginsInstalled": {
"id": "app.components.LeftMenuLinkContainer.noPluginsInstalled",
"defaultMessage": "No plugins installed yet"
}
}

View File

@ -1,40 +0,0 @@
// Import
@import "../../styles/variables/variables";
.leftMenuLinkContainer { /* stylelint-ignore */
padding-top: .6rem;
padding-bottom: 10.2rem; // LeftMenuFooter height
position: absolute;
top: 60px;
right: 0;
bottom: 0;
left: 0;
overflow-y: auto;
}
.title {
padding-left: 2rem;
padding-right: 1.6rem;
padding-top: 1rem;
margin-bottom: .8rem;
color: $left-menu-title-color;
text-transform: uppercase;
font-size: 1.1rem;
letter-spacing: .1rem;
font-weight: 800;
}
.list {
list-style: none;
padding: 0;
margin-bottom: 2rem;
}
.noPluginsInstalled {
color: $white;
padding-left: 1.6rem;
padding-right: 1.6rem;
font-weight: 300;
min-height: 3.6rem;
padding-top: .9rem;
}

View File

@ -1,77 +0,0 @@
/*
*
*
* ListPlugins
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { map, size } from 'lodash';
// Design
import { Button } from 'strapi-helper-plugin';
import Row from '../Row';
import styles from './styles.scss';
class ListPlugins extends React.Component {
render() {
const listSize = size(this.props.plugins);
let titleType = listSize === 1 ? 'singular' : 'plural';
if (listSize === 0) {
titleType = 'none';
}
return (
<div className={styles.container}>
<div className={styles.titleContainer}>
<div>
<FormattedMessage
id={`app.components.listPlugins.title.${titleType}`}
values={{ number: listSize }}
/>
</div>
<div>
<Button
label="app.components.listPlugins.button"
onClick={() => this.props.history.push('/install-plugin')}
secondaryHotlineAdd
style={{ display: 'none' }}
/>
</div>
</div>
<div className="container-fluid">
<div className="row">
<div className={styles.ulContainer}>
<ul>
{map(this.props.plugins, (plugin, key) => (
<Row
name={key}
key={plugin.name}
plugin={plugin}
onDeleteClick={this.props.onDeleteClick}
pluginActionSucceeded={this.props.pluginActionSucceeded}
onDeleteConfirm={this.props.onDeleteConfirm}
/>
))}
</ul>
</div>
</div>
</div>
</div>
);
}
}
ListPlugins.propTypes = {
history: PropTypes.object.isRequired,
onDeleteClick: PropTypes.func.isRequired,
onDeleteConfirm: PropTypes.func.isRequired,
pluginActionSucceeded: PropTypes.bool.isRequired,
plugins: PropTypes.object.isRequired,
};
export default ListPlugins;

View File

@ -1,76 +0,0 @@
.container {
padding-top: 1.6rem;
background-color: #FFFFFF;
box-shadow: 0 2px 4px #E3E9F3;
}
.titleContainer {
display: flex;
justify-content: space-between;
padding-right: 1.8rem;
padding-left: 1.8rem;
font-size: 1.8rem;
font-weight: bold;
}
.ulContainer {
width: 100%;
padding-top: 1.5rem;
> ul {
margin: 0;
padding: 0;
list-style: none;
> li {
height: 5.4rem !important;
line-height: 5.4rem !important;
padding-right: 3.2rem;
padding-left: 1.5rem;
> div:first-child {
margin: 0;
> div:first-child {
padding-left: 0!important;
// margin-right: 1.5rem;
}
}
}
> li:last-child {
> div {
border-bottom: none;
}
}
}
}
.pluginContent {
text-align: left !important;
> span:first-child {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.7px;
text-transform: uppercase;
}
> span:last-child {
font-size: 13px;
}
}
.icoContainer {
width: 70px;
height: 36px;
margin: auto 0;
line-height: 36px;
text-align: center;
border: 1px solid rgba(28,93,231,0.1);
border-radius: 3px;
font-size: 20px;
}
.actionContainer {
display: flex;
justify-content: flex-end;
}
.nameWrapper {
display: flex;
}

View File

@ -1,78 +0,0 @@
/**
*
* Logout
*
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import { get } from 'lodash';
import PropTypes from 'prop-types';
import {
ButtonDropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
} from 'reactstrap';
import { auth } from 'strapi-helper-plugin';
import styles from './styles.scss';
class Logout extends React.Component {
// eslint-disable-line react/prefer-stateless-function
state = { isOpen: false };
handleGoTo = () => {
const id = get(auth.getUserInfo(), 'id') || get(auth.getUserInfo(), '_id');
this.props.history.push({
pathname: `/plugins/content-manager/administrator/${id}`,
search:
'?redirectUrl=/plugins/content-manager/administrator/?page=0&limit=0&sort=id&source=admin',
});
};
handleGoToAdministrator = () => {
this.props.history.push({
pathname: '/plugins/content-manager/administrator',
search: '?source=admin',
});
};
handleLogout = () => {
auth.clearAppStorage();
this.props.history.push('/plugins/users-permissions/auth/login');
};
toggle = () => this.setState({ isOpen: !this.state.isOpen });
render() {
return (
<div className={styles.logout}>
<ButtonDropdown isOpen={this.state.isOpen} toggle={this.toggle}>
<DropdownToggle>
{get(auth.getUserInfo(), 'username')}
<i className="fa fa-caret-down" alt={`${this.state.isOpen}`} />
</DropdownToggle>
<DropdownMenu className={styles.dropDownContent}>
<DropdownItem onClick={this.handleGoTo} className={styles.item}>
<FormattedMessage id="app.components.Logout.profile" />
</DropdownItem>
<DropdownItem
onClick={this.handleGoToAdministrator}
className={styles.item}
>
<FormattedMessage id="app.components.Logout.admin" />
</DropdownItem>
<DropdownItem onClick={this.handleLogout}>
<FormattedMessage id="app.components.Logout.logout" />
<i className="fa fa-sign-out" />
</DropdownItem>
</DropdownMenu>
</ButtonDropdown>
</div>
);
}
}
export default withRouter(Logout);

View File

@ -1,113 +0,0 @@
.logout {
position: relative;
min-width: 19rem;
-webkit-font-smoothing: antialiased;
> div {
height: 6rem;
width: 100%;
line-height: 5.8rem;
z-index: 999;
> button {
width: 100%;
padding-right: 20px;
background: transparent;
border: none;
border-radius: 0;
color: #333740;
font-size: 14px;
font-weight: 500;
text-align: right;
cursor: pointer;
transition: background .2s ease-out;
&:hover, &:focus, &:active {
color: #333740;
background-color: #FAFAFB !important;
}
> i {
margin-left: 10px;
transition: transform .3s ease-out;
&[alt="true"] {
transform: rotateX(180deg);
}
}
}
}
&:after {
position: absolute;
right: -1px;
top: calc(50% - 10px);
content: '';
display: inline-block;
vertical-align: middle;
height: 20px;
border-left: 1px solid #F3F4F4;
transition: opacity .2s ease-out;
}
&:hover:after {
opacity: 0;
}
}
.dropDownContent {
left: auto !important;
min-width: 100%!important;
margin: 0 !important;
padding: 0;
line-height: 1.8rem;
border: none!important;
border-top-left-radius: 0!important;
border-top-right-radius: 0!important;
font-size: 14px;
overflow: hidden;
box-shadow: 0 1px 4px 0px rgba(40, 42, 49, 0.05);
&:before{
content: '';
position: absolute;
top: -3px;
left: -1px;
width: calc(100% + 1px);
height: 3px;
box-shadow: 0 1px 2px 0 rgba(40, 42, 49, 0.16);
}
> button {
height: 40px;
padding: 0px 15px;
line-height: 40px;
&:hover, &:focus, &:active {
background-color: #FAFAFB!important;
border-radius: 0px;
cursor: pointer;
}
}
> button {
height: 44px;
line-height: 48px;
&:hover, &:active {
color: #333740;
}
}
> button:last-child {
color: #F75B1D;
> i {
margin-left: 10px
}
}
}
.item {
&:active {
color: black;
}
&:hover {
background-color: #FAFAFB !important;
}
}

View File

@ -1,86 +0,0 @@
/**
*
* Notification
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isObject } from 'lodash';
import styles from './styles.scss';
class Notification extends React.Component { // eslint-disable-line react/prefer-stateless-function
handleCloseClicked = () => {
this.props.onHideNotification(this.props.notification.id);
};
options = {
success: {
icon: 'fa-check',
title: 'Success',
class: 'notificationSuccess',
},
warning: {
icon: 'fa-exclamation',
title: 'Warning',
class: 'notificationWarning',
},
error: {
icon: 'fa-exclamation',
title: 'Error',
class: 'notificationError',
},
info: {
icon: 'fa-info',
title: 'Info',
class: 'notificationInfo',
},
};
render() {
const options = this.options[this.props.notification.status] || this.options.info;
const { notification: { message } } = this.props;
const content = isObject(message) && message.id ?
<FormattedMessage id={message.id} defaultMessage={message.id} values={message.values} />
: <FormattedMessage id={message} defaultMessage={message} />;
return (
<li key={this.props.notification.id} className={`${styles.notification} ${styles[options.class]}`} onClick={this.handleCloseClicked}>
<i className={`fa ${options.icon} ${styles.notificationIcon}`} />
<div className={styles.notificationContent}>
<p className={styles.notificationTitle}>
{content}
</p>
</div>
<i className={`fa fa-close ${styles.notificationClose}`} onClick={this.handleCloseClicked} />
</li>
);
}
}
Notification.defaultProps = {
notification: {
id: 1,
message: 'app.utils.defaultMessage',
status: 'success',
},
};
Notification.propTypes = {
notification: PropTypes.shape({
id: PropTypes.number,
message: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
id: PropTypes.string,
values: PropTypes.object,
}),
]),
status: PropTypes.string,
}),
onHideNotification: PropTypes.func.isRequired,
};
export default Notification;

View File

@ -1,125 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.notification {
position: relative;
display: flex;
align-items: stretch;
width: 300px;
min-height: 60px;
margin-bottom: 14px;
background: $white;
border-radius: 2px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.15);
color: #333740;
transition: all 0.15s ease;
overflow: hidden;
z-index: 10;
// The last notification must appear from
// the background of the previous one.
&:last-child {
z-index: 1;
}
}
.notification:hover {
cursor: pointer;
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
}
.notificationIcon {
position: relative;
display: block;
width: 60px;
text-align: center;
font-size: 2.4rem;
&:before {
position: absolute;
top: calc(50% - 10px); left: calc(50% - 10px);
width: 20px;
height: 20px;
padding-top: 4px;
border-radius: 100%;
border: 1px solid $brand-success;
color: $brand-success;
font-size: 1.2rem;
text-align: center;
}
}
.notificationContent {
display: flex;
align-items: center;
width: 220px;
margin: 0;
padding-right: 10px;
border-right: 1px solid rgba(255, 255, 255, 0.3);
}
.notificationTitle {
margin-bottom: 0;
font-size: 1.4rem;
font-weight: 400;
line-height: 1.8rem;
}
.notificationClose {
cursor: pointer;
opacity: 0.6;
position: relative;
display: block;
width: 20px;
font-size: 1.4rem;
color: #BBC2BF;
transition: opacity 0.1s ease;
-webkit-font-smoothing: antialiased;
&:hover {
opacity: 1;
}
&:before {
position: absolute;
top: calc(50% - 6px);
height: 100%;
font-size: 1.3rem;
font-weight: 100!important;
}
}
.notificationSuccess{
background: linear-gradient(100deg , #FFFFFF 50%, rgba(39, 183, 15, .05)), $white;
}
.notificationWarning {
background: linear-gradient(100deg , #FFFFFF 50%, rgba(250, 156, 0, .05)), $white;
.notificationIcon:before {
padding-top: 4px;
border-color: $brand-warning;
color: $brand-warning;
}
}
.notificationError {
background: linear-gradient(100deg , #FFFFFF 50%, rgba(255, 93, 0, .05)), $white;
.notificationIcon:before {
padding-top: 4px;
border-color: $brand-danger;
color: $brand-danger;
}
}
.notificationInfo {
background: linear-gradient(100deg , #FFFFFF 50%, rgba(28, 93, 231, .05)), $white;
.notificationIcon:before {
border-color: $brand-primary;
color: $brand-primary;
}
}

View File

@ -1,64 +0,0 @@
/**
*
* NotificationsContainer
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import Notification from '../Notification';
import styles from './styles.scss';
class NotificationsContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
render() {
if (this.props.notifications.length === 0) {
return (false);
}
const notifications = this.props.notifications.map((notification, i) => (
<CSSTransition
key={i}
classNames="notification"
timeout={{
enter: 500,
exit: 300,
}}
>
<Notification
key={notification.id}
onHideNotification={this.props.onHideNotification}
notification={notification}
/>
</CSSTransition>
));
return (
<TransitionGroup className={styles.notificationsContainer}>
{notifications}
</TransitionGroup>
);
}
}
NotificationsContainer.defaultProps = {
notifications: [
{
id: 1,
message: 'app.utils.defaultMessage',
status: 'success',
},
],
};
NotificationsContainer.propTypes = {
notifications: PropTypes.oneOfType([
PropTypes.object,
PropTypes.array,
]),
onHideNotification: PropTypes.func.isRequired,
};
export default NotificationsContainer;

View File

@ -1,14 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.notificationsContainer { /* stylelint-disable */
position: fixed;
top: 72px;
left: 240px;
right: 0;
z-index: 1000;
list-style: none;
width: 300px;
margin: 0 auto;
overflow-y: hidden;
}

View File

@ -1,29 +0,0 @@
/*
*
* Official
*
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import styles from './styles.scss';
function Official(props) {
return (
<button className={styles.wrapper} style={props.style}>
<i className="fa fa-star" />
<FormattedMessage id="app.components.Official" />
</button>
);
}
Official.defaultProps = {
style: {},
};
Official.propTypes = {
style: PropTypes.object,
};
export default Official;

View File

@ -1,29 +0,0 @@
.wrapper {
display: flex;
// justify-content: space-between;
height: 20px !important;
width: 88px;
padding: 0 10px;
border-radius: 2px;
background-color: #EE8948;
line-height: 20px;
text-align: center;
text-transform: uppercase;
> span {
height: 20px;
padding: 0!important;
color: #fff;
letter-spacing: 0.5px;
font-weight: 600;
font-size: 11px;
}
> i {
margin-top: 1px;
margin-right: 6px;
vertical-align: -webkit-baseline-middle;
color: #FFDC00;
font-size: 10px;
}
}

View File

@ -1,176 +0,0 @@
/**
*
* OnboardingList
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { Player } from 'video-react';
import '../../../../node_modules/video-react/dist/video-react.css';
import styles from './styles.scss';
class OnboardingVideo extends React.Component {
componentDidMount() {
this.hiddenPlayer.current.subscribeToStateChange(this.handleChangeState);
}
hiddenPlayer = React.createRef();
player = React.createRef();
handleChangeState = (state, prevState) => {
const { duration } = state;
const { id } = this.props;
if (duration !== prevState.duration) {
this.props.setVideoDuration(id, duration);
}
};
handleChangeIsPlayingState = (state, prevState) => {
const { isActive } = state;
const { id } = this.props;
if (isActive !== prevState.isActive && isActive) {
this.props.didPlayVideo(id, this.props.video.startTime);
}
};
handleCurrentTimeChange = curr => {
this.props.getVideoCurrentTime(
this.props.id,
curr,
this.props.video.duration,
);
};
handleModalOpen = () => {
this.player.current.subscribeToStateChange(this.handleChangeIsPlayingState);
this.player.current.play();
if (this.props.video.startTime === 0) {
const { player } = this.player.current.getState();
player.isActive = true;
this.props.didPlayVideo(this.props.id, this.props.video.startTime);
} else {
this.player.current.pause();
}
};
handleVideoPause = () => {
const { player } = this.player.current.getState();
const currTime = player.currentTime;
this.handleCurrentTimeChange(currTime);
this.props.didStopVideo(this.props.id, currTime);
};
handleModalClose = () => {
const { player } = this.player.current.getState();
const paused = player.paused;
if (!paused) {
this.handleVideoPause();
}
};
render() {
const { video } = this.props;
return (
<li
key={this.props.id}
onClick={this.props.onClick}
id={this.props.id}
className={cn(styles.listItem, video.end && styles.finished)}
>
<div className={styles.thumbWrapper}>
<img src={video.preview} alt="preview" />
<div className={styles.overlay} />
<div className={styles.play} />
</div>
<div className={styles.txtWrapper}>
<p className={styles.title}>{video.title}</p>
<p className={styles.time}>
{isNaN(video.duration)
? '\xA0'
: `${Math.floor(video.duration / 60)}:${Math.floor(
video.duration,
) % 60}`}
</p>
</div>
<Modal
isOpen={video.isOpen}
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className={styles.videoModal}
onOpened={this.handleModalOpen}
onClosed={this.handleModalClose}
>
<ModalHeader
toggle={this.props.onClick} // eslint-disable-line react/jsx-handler-names
className={styles.videoModalHeader}
>
{video.title}
</ModalHeader>
<ModalBody className={styles.modalBodyHelper}>
<div>
<Player
ref={this.player}
className={styles.videoPlayer}
// poster="/assets/poster.png"
src={video.video}
startTime={video.startTime}
preload="auto"
onPause={this.handleVideoPause}
onplay={this.videoStart}
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
</ModalBody>
</Modal>
{!this.props.video.duration && (
<div className={cn(styles.hiddenPlayerWrapper)}>
<Player
ref={this.hiddenPlayer}
// poster="/assets/poster.png"
src={video.video}
preload="auto"
subscribeToStateChange={this.subscribeToStateChange}
/>
</div>
)}
</li>
);
}
}
OnboardingVideo.defaultProps = {
didPlayVideo: () => {},
didStopVideo: () => {},
getVideoCurrentTime: () => {},
id: 0,
onClick: () => {},
setVideoDuration: () => {},
video: {},
};
OnboardingVideo.propTypes = {
didPlayVideo: PropTypes.func,
didStopVideo: PropTypes.func,
getVideoCurrentTime: PropTypes.func,
id: PropTypes.number,
onClick: PropTypes.func,
setVideoDuration: PropTypes.func,
video: PropTypes.object,
};
export default OnboardingVideo;

View File

@ -1,144 +0,0 @@
li.listItem {
display: block;
padding: 17px 15px;
cursor: pointer;
&:hover {
background-color: #f7f8f8;
.title {
color: #0e7de7;
}
}
.txtWrapper,
.thumbWrapper {
display: inline-block;
vertical-align: middle;
}
.thumbWrapper {
position: relative;
width: 55px;
height: 38px;
background-color: #d8d8d8;
border-radius: 2px;
overflow: hidden;
.overlay {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
background-color: rgba(#0E7DE7, .8);
}
img {
position: relative;
z-index: 0;
width: 100%;
height: 100%;
}
.play {
position: absolute;
top: calc(50% - 10px);
left: calc(50% - 10px);
width: 20px;
height: 20px;
background-color: #0e7de7;
border: 1px solid white;
text-align: center;
line-height: 20px;
border-radius: 50%;
z-index: 2;
&::before {
content: '\f04b';
display: inline-block;
vertical-align: top;
height: 100%;
font-family: 'FontAwesome';
color: white;
font-size: 10px;
margin-left: 3px;
line-height: 18px;
}
}
}
&.finished {
.title {
color: #919bae;
}
.thumbWrapper {
.overlay {
background-color: transparent;
}
img {
opacity: 0.6;
}
.play {
background-color: #5a9e06;
border-color: #5a9e06;
&::before {
content: '\f00c';
margin-left: 0;
font-size: 11px;
line-height: 20px;
}
}
}
}
.txtWrapper {
padding: 0 15px;
p {
font-size: 14px;
line-height: 24px;
font-family: Lato;
font-weight: 600;
}
.time {
color: #919bae;
font-family: Lato;
font-weight: bold;
font-size: 11px;
line-height: 11px;
}
}
}
.hiddenPlayerWrapper {
display: none;
}
.videoModal {
margin-right: auto !important;
margin-left: auto !important;
.videoModalHeader {
padding-bottom: 0;
border-bottom: 0;
> h5 {
font-family: Lato;
font-weight: bold!important;
font-size: 1.8rem!important;
line-height: 3.1rem;
color: #333740;
}
> button {
display: flex;
position: absolute;
right: 0;
top: 0;
margin-top: 0;
margin-right: 0;
padding: 10px;
cursor: pointer;
span {
line-height: 0.6em;
}
}
}
.videoPlayer {
> button {
top: 50%;
margin-top: -0.75em;
left: 50%;
margin-left: -1.5em;
}
}
}

View File

@ -1,227 +0,0 @@
/**
*
* PluginCard
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { isEmpty, replace } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { Button } from 'strapi-helper-plugin';
import InstallPluginPopup from '../InstallPluginPopup';
import styles from './styles.scss';
const PLUGINS_WITH_CONFIG = ['content-manager', 'email', 'upload'];
/* eslint-disable react/no-unused-state */
class PluginCard extends React.Component {
state = {
boostrapCol: 'col-lg-4',
};
componentDidMount() {
// Listen window resize.
window.addEventListener('resize', this.setBoostrapCol);
this.setBoostrapCol();
}
componentWillUnmount() {
window.removeEventListener('resize', this.setBoostrapCol);
}
setBoostrapCol = () => {
let boostrapCol = 'col-lg-4';
if (window.innerWidth > 1680) {
boostrapCol = 'col-lg-3';
}
if (window.innerWidth > 2300) {
boostrapCol = 'col-lg-2';
}
this.setState({ boostrapCol });
};
handleClick = () => {
if (this.props.plugin.id !== 'support-us') {
this.props.history.push({
pathname: this.props.history.location.pathname,
hash: `${this.props.plugin.id}::description`,
});
} else {
this.aTag.click();
}
};
handleClickSettings = e => {
const settingsPath =
this.props.plugin.id === 'content-manager'
? '/plugins/content-manager/ctm-configurations'
: `/plugins/${this.props.plugin.id}/configurations/${
this.props.currentEnvironment
}`;
e.preventDefault();
e.stopPropagation();
this.props.history.push(settingsPath);
};
handleDownloadPlugin = e => {
if (
!this.props.isAlreadyInstalled &&
this.props.plugin.id !== 'support-us'
) {
this.props.downloadPlugin(e);
} else if (this.props.plugin.id === 'support-us') {
this.aTag.click();
} else {
this.props.history.push('/list-plugins');
}
};
render() {
const buttonClass = !this.props.isAlreadyInstalled
? styles.primary
: styles.secondary;
const buttonLabel = this.props.isAlreadyInstalled
? 'app.components.PluginCard.Button.label.install'
: 'app.components.PluginCard.Button.label.download';
// Display settings link for a selection of plugins.
const settingsComponent = PLUGINS_WITH_CONFIG.includes(
this.props.plugin.id,
) && (
<div className={styles.settings} onClick={this.handleClickSettings}>
<i className="fa fa-cog" />
<FormattedMessage id="app.components.PluginCard.settings" />
</div>
);
const descriptions = {
short:
this.props.plugin.id === 'support-us' ? (
<FormattedMessage id={this.props.plugin.description.short} />
) : (
this.props.plugin.description.short
),
long:
this.props.plugin.id === 'support-us' ? (
<FormattedMessage
id={
this.props.plugin.description.long ||
this.props.plugin.description.short
}
/>
) : (
this.props.plugin.description.long ||
this.props.plugin.description.short
),
};
return (
<div className={cn(this.state.boostrapCol, styles.pluginCard)}>
<div className={styles.wrapper}>
<div className={styles.cardTitle}>
<div className={styles.frame}>
<span className={styles.helper} />
<img src={this.props.plugin.logo} alt="icon" />
</div>
<div>
{this.props.plugin.name}{' '}
<i
className="fa fa-external-link"
onClick={() =>
window.open(
`https://github.com/strapi/strapi/tree/master/packages/strapi-plugin-${
this.props.plugin.id
}`,
'_blank',
)
}
/>
</div>
</div>
<div className={styles.cardDescription}>
{descriptions.long}
{/* &nbsp;<FormattedMessage id="app.components.PluginCard.more-details" /> */}
</div>
<div className={styles.cardFooter} onClick={e => e.stopPropagation()}>
<div className={styles.cardFooterButton}>
<Button
className={cn(buttonClass, styles.button)}
label={buttonLabel}
onClick={this.handleDownloadPlugin}
/>
<a
href="https://strapi.io/shop"
style={{ display: 'none' }}
ref={a => {
this.aTag = a;
}}
target="_blank"
>
&nbsp;
</a>
</div>
{this.props.isAlreadyInstalled ? (
settingsComponent
) : (
<div className={styles.compatible}>
<i
className={`fa fa-${
this.props.plugin.isCompatible ? 'check' : 'times'
}`}
/>
<FormattedMessage
id={`app.components.PluginCard.compatible${
this.props.plugin.id === 'support-us' ? 'Community' : ''
}`}
/>
</div>
)}
</div>
</div>
<InstallPluginPopup
history={this.props.history}
isAlreadyInstalled={this.props.isAlreadyInstalled}
isOpen={
!isEmpty(this.props.history.location.hash) &&
replace(
this.props.history.location.hash.split('::')[0],
'#',
'',
) === this.props.plugin.id
}
plugin={this.props.plugin}
/>
</div>
);
}
}
PluginCard.defaultProps = {
isAlreadyInstalled: false,
plugin: {
description: '',
id: '',
name: '',
price: 0,
ratings: 5,
},
};
PluginCard.propTypes = {
currentEnvironment: PropTypes.string.isRequired,
downloadPlugin: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
isAlreadyInstalled: PropTypes.bool,
plugin: PropTypes.object,
};
export default PluginCard;

View File

@ -1,146 +0,0 @@
.wrapper {
position: relative;
min-height: 216px;
margin-bottom: 3.6rem;
padding: 1.2rem 1.5rem;
padding-bottom: 0;
background-color: #fff;
box-shadow: 0 2px 4px #E3E9F3;
-webkit-font-smoothing: antialiased;
}
.cardTitle {
display: flex;
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
> div:first-child {
margin-right: 14px;
}
> div:last-child {
height: 36px;
line-height: 36px;
}
i {
margin-left: 7px;
color: #B3B5B9;
font-size: 1rem;
vertical-align: baseline;
cursor: pointer;
}
}
.cardDescription {
height: 54px;
margin-top: 27px;
margin-bottom: 9px;
font-size: 13px;
font-weight: 400;
> span:last-child {
color: #1C5DE7;
}
-webkit-font-smoothing: antialiased;
}
.cardFooter {
position: absolute;
bottom: 0; left: 0;
display: flex;
width: 100%;
height: 45px;
padding: 0.9rem 1.5rem 1rem;
background-color: #FAFAFB;
justify-content: space-between;
flex-direction: row-reverse;
cursor: initial;
}
.compatible {
margin-top: 3px;
color: #5A9E06;
font-size: 1.3rem;
font-style: italic;
> i {
margin-right: 7px;
font-size: 12px;
}
}
.settings{
margin-top: 3px;
color: #323740;
font-size: 1.3rem;
font-weight: 500;
cursor: pointer;
> i {
margin-right: 7px;
font-size: 12px;
}
}
.button {
height: 26px;
min-width: 89px !important;
padding: 0 15px;
margin: 0;
border-radius: 2px !important;
line-height: 24px;
font-size: 13px;
font-weight: 500 !important;
cursor: pointer;
span {
display: inline-block;
width: 100%;
height: 100%;
line-height: 24px;
}
}
.frame {
width: 70px;
height: 36px;
margin: auto 0;
text-align: center;
border: 1px solid #F3F3F7;
border-radius: 3px;
white-space: nowrap;
> img {
max-height: 36px;
vertical-align: middle;
}
}
.helper {
display: inline-block;
height: 100%;
vertical-align: middle;
}
.pluginCard {
}
.primary {
background: linear-gradient(315deg, #0097F6 0%, #005EEA 100%);
color: white;
font-weight: 500;
-webkit-font-smoothing: antialiased;
&:active {
box-shadow: inset 1px 1px 3px rgba(0,0,0,.15);
}
}
.secondary {
border: 1px solid #DFE0E1;
font-weight: 600;
}

View File

@ -1,120 +0,0 @@
/*
*
* Row
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import { includes, isEmpty } from 'lodash';
// Design
import { IcoContainer, ListRow, PopUpWarning } from 'strapi-helper-plugin';
import styles from './styles.scss';
const PLUGINS_WITH_CONFIG = ['content-manager', 'email', 'upload'];
class Row extends React.Component {
state = { showModal: false };
componentWillReceiveProps(nextProps) {
if (nextProps.pluginActionSucceeded !== this.props.pluginActionSucceeded) {
this.setState({ showModal: false });
}
}
handleClick = e => {
this.setState({ showModal: !this.state.showModal });
this.props.onDeleteClick(e);
};
render() {
// const uploadPath = `/plugins/upload/configurations/${this.context.currentEnvironment}`;
// Make sure to match the ctm config URI instead of content-type view URI
const settingsPath =
this.props.name === 'content-manager'
? '/plugins/content-manager/ctm-configurations'
: `/plugins/${this.props.name}/configurations/${
this.context.currentEnvironment
}`;
// const icons = this.props.name === 'upload' || this.props.name === 'email' ? [
const icons = includes(PLUGINS_WITH_CONFIG, this.props.name)
? [
{
icoType: 'cog',
onClick: e => {
e.preventDefault();
e.stopPropagation();
this.props.history.push(settingsPath);
},
},
{
icoType: 'trash',
id: this.props.name,
onClick: this.handleClick,
},
]
: [
{
icoType: 'trash',
id: this.props.name,
onClick: this.handleClick,
},
];
return (
<ListRow>
<div className={cn('col-md-11', styles.nameWrapper)}>
<div className={styles.icoContainer} style={{ marginRight: '14px' }}>
{!isEmpty(this.props.plugin.logo) && (
<img src={`${this.props.plugin.logo}`} alt="icon" />
)}
{isEmpty(this.props.plugin.logo) && (
<div className={styles.icoWrapper}>
<i className={`fa fa-${this.props.plugin.icon}`} />
</div>
)}
</div>
<div className={styles.pluginContent}>
<span>{this.props.plugin.name} &nbsp;</span>
<FormattedMessage
id={`${this.props.plugin.description}.short`}
defaultMessage={this.props.plugin.description}
/>
</div>
</div>
<div className="col-md-1">
<div className={styles.actionContainer}>
<IcoContainer icons={icons} />
</div>
</div>
<PopUpWarning
isOpen={this.state.showModal}
toggleModal={() =>
this.setState({ showModal: !this.state.showModal })
}
popUpWarningType="danger"
onConfirm={this.props.onDeleteConfirm}
/>
</ListRow>
);
}
}
Row.contextTypes = {
currentEnvironment: PropTypes.string,
};
Row.propTypes = {
name: PropTypes.string.isRequired,
onDeleteClick: PropTypes.func.isRequired,
onDeleteConfirm: PropTypes.func.isRequired,
plugin: PropTypes.object.isRequired,
pluginActionSucceeded: PropTypes.bool.isRequired,
};
export default withRouter(Row);

View File

@ -1,74 +0,0 @@
.nameWrapper {
display: flex;
}
.icoContainer {
width: 70px;
height: 36px;
position: relative;
margin: auto 0;
text-align: center;
background: #FAFAFB;
border: 1px solid #F3F3F7;
border-radius: 3px;
font-size: 20px;
> img {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
}
.pluginContent {
text-align: left !important;
> span:first-child {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.7px;
text-transform: uppercase;
}
> span:last-child {
font-size: 13px;
}
}
.actionContainer {
display: flex;
justify-content: flex-end;
}
.frame {
width: 70px;
height: 36px;
margin: auto 0;
text-align: center;
border: 1px solid rgba(28,93,231,0.1);
border-radius: 3px;
white-space: nowrap;
> img {
max-height: 36px;
vertical-align: baseline;
}
}
.helper {
display: inline-block;
height: 100%;
vertical-align: middle;
}
.icoWrapper {
display: flex;
height: 100%;
flex-direction: column;
justify-content: space-around;
}

View File

@ -1,38 +0,0 @@
/*
*
* StarsContainer
*
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { map, times } from 'lodash';
import styles from './styles.scss';
function StarsContainer({ ratings }) {
const stars = Math.round(ratings);
const coloredStars = times(stars, String);
const emptyStars = times(5 - stars, String);
return (
<div className={styles.starsContainer}>
<div>
{map(coloredStars, star => <i key={star} className="fa fa-star" />)}
</div>
<div>
{map(emptyStars, s => <i key={s} className="fa fa-star" />)}
</div>
</div>
);
}
StarsContainer.defaultProps = {
ratings: 5,
};
StarsContainer.propTypes = {
ratings: PropTypes.number,
};
export default StarsContainer;

View File

@ -1,13 +0,0 @@
.starsContainer {
display: flex;
margin-right: 10px;
> div {
color: #EEA348;
> i {
margin-right: 2px;
}
}
> div:last-child {
color: #B3B5B9;
}
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import LoadingIndicator from 'components/LoadingIndicator';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,75 +0,0 @@
/**
*
* Sub
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { isFunction, isObject } from 'lodash';
import cn from 'classnames';
import { LoadingBar } from 'strapi-helper-plugin';
import styles from './styles.scss';
function Sub({ bordered, content, link, name, style, title, underline }) {
if (isObject(title)) {
return (
<div className={cn(styles.subWrapper, bordered && styles.subBordered)}>
<FormattedMessage {...title}>
{message => (
<span className={cn(underline && styles.underlinedTitle)}>
{message}
{name}
</span>
)}
</FormattedMessage>
{content()}
</div>
);
}
return (
<a
className={cn(
styles.subWrapper,
bordered && styles.subBordered,
styles.link,
)}
href={`https://blog.strapi.io/${link}`}
target="_blank"
>
<span>{title}</span>
{title === '' && <LoadingBar />}
{content === '' && <LoadingBar style={{ width: '40%' }} />}
<p style={style}>{isFunction(content) ? content() : content}</p>
</a>
);
}
Sub.defaultProps = {
bordered: false,
content: () => '',
link: '',
name: '',
style: {},
title: {
id: 'app.utils.defaultMessage',
defaultMessage: 'app.utils.defaultMessage',
values: {},
},
underline: false,
};
Sub.propTypes = {
bordered: PropTypes.bool,
content: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
link: PropTypes.string,
name: PropTypes.string,
style: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
underline: PropTypes.bool,
};
export default Sub;

View File

@ -1,46 +0,0 @@
.subBordered {
margin-bottom: 18px;
border-bottom: 1px solid #F7F8F8;
}
.subWrapper {
position: relative;
line-height: 18px;
text-decoration: none;
> span {
text-decoration: none;
font-family: Lato-Bold;
font-size: 20px;
color: #333740;
letter-spacing: 0;
transition: color .2s ease;
}
p {
text-decoration: none;
display: block;
max-width: calc(100% - 150px);
margin-top: 18px;
color: #333740;
font-size: 14px;
transition: color .2s ease;
}
}
.underlinedTitle {
border-bottom: 3px solid #F0B41E;
}
.link{
&:hover, &:focus, &:active{
text-decoration: none;
}
&:hover{
> span, p {
color: lighten(#333740, 20%);
}
}
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import { LoadingIndicator } from 'strapi-helper-plugin';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,23 +0,0 @@
/**
*
* SupportUsCta
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
function SupportUsCta() {
return (
<FormattedMessage id="app.components.HomePage.support.link">
{message => (
<a href="https://strapi.io/shop" target="_blank" className={styles.supportUsCta}>
{message}
</a>
)}
</FormattedMessage>
);
}
export default SupportUsCta;

View File

@ -1,18 +0,0 @@
.supportUsCta {
padding: 7px 12px 7px 20px;
border: 1px solid #FFFFFF;
border-radius: 3px;
color: #FFFFFF;
font-size: 13px;
font-weight: 800;
letter-spacing: 0.5px;
&:after {
content: '\f178';
font-family: 'FontAwesome';
margin-left: 10px;
}
&:hover {
color: #FFFFFF;
text-decoration: none;
}
}

View File

@ -1,8 +0,0 @@
import Loadable from 'react-loadable';
import { LoadingIndicator } from 'strapi-helper-plugin';
export default Loadable({
loader: () => import('./index'),
loading: LoadingIndicator,
});

View File

@ -1,19 +0,0 @@
/**
*
* SupportUsTitle
*/
import React from 'react';
import { FormattedMessage } from 'react-intl';
import styles from './styles.scss';
function SupportUsTitle() {
return (
<FormattedMessage id="app.components.HomePage.support">
{message => <span className={styles.supportUsTitle}>{message}</span>}
</FormattedMessage>
);
}
export default SupportUsTitle;

View File

@ -1,11 +0,0 @@
.supportUsTitle {
color: #FFFFFF;
font-size: 18px;
font-weight: 800;
letter-spacing: 0.5px;
&:after {
content: '❤️';
margin-left: 7px;
font-size: 18px;
}
}

View File

@ -1,3 +0,0 @@
{
"languages": ["en", "ar", "es", "fr", "de", "it", "ko", "nl", "pl", "pt", "pt-BR", "ru", "tr", "zh", "zh-Hans", "ja"]
}

View File

@ -1,56 +0,0 @@
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
// import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware];
const enhancers = [applyMiddleware(...middlewares)];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
// Prevent recomputing reducers for `replaceReducer`
shouldHotReload: false,
name: 'Strapi - Dashboard',
})
: compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}

View File

@ -1,24 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const style = {
position: 'fixed',
top: '0',
right: '0',
display: 'flex',
zIndex: '1050',
};
function NavWrapper({ children }) {
return <div style={style}>{children}</div>;
}
NavWrapper.propTypes = {
children: PropTypes.node,
};
NavWrapper.defaultProps = {
children: null,
};
export default NavWrapper;

View File

@ -1,96 +0,0 @@
/*
*
* Admin actions
*
*/
import {
EMIT_EVENT,
GET_INIT_DATA,
GET_INIT_DATA_SUCCEEDED,
GET_SECURED_DATA,
GET_SECURED_DATA_SUCCEEDED,
HIDE_LEFT_MENU,
HIDE_LOGOUT,
SET_APP_ERROR,
SET_APP_SECURED,
SHOW_LEFT_MENU,
SHOW_LOGOUT,
UNSET_APP_SECURED,
} from './constants';
export function emitEvent(event, properties) {
return {
type: EMIT_EVENT,
event,
properties,
};
}
export function getInitData() {
return {
type: GET_INIT_DATA,
};
}
export function getInitDataSucceeded(data) {
return {
type: GET_INIT_DATA_SUCCEEDED,
data,
};
}
export function getSecuredData() {
return {
type: GET_SECURED_DATA,
};
}
export function getSecuredDataSucceeded(data) {
return {
type: GET_SECURED_DATA_SUCCEEDED,
data,
};
}
export function hideLeftMenu() {
return {
type: HIDE_LEFT_MENU,
};
}
export function hideLogout() {
return {
type: HIDE_LOGOUT,
};
}
export function setAppError() {
return {
type: SET_APP_ERROR,
};
}
export function setAppSecured() {
return {
type: SET_APP_SECURED,
};
}
export function showLeftMenu() {
return {
type: SHOW_LEFT_MENU,
};
}
export function showLogout() {
return {
type: SHOW_LOGOUT,
};
}
export function unsetAppSecured() {
return {
type: UNSET_APP_SECURED,
};
}

View File

@ -1,20 +0,0 @@
/*
*
* Admin constants
*
*/
export const EMIT_EVENT = 'app/Admin/EMIT_EVENT';
export const GET_INIT_DATA = 'StrapiAdmin/Admin/GET_INIT_DATA';
export const GET_INIT_DATA_SUCCEEDED =
'StrapiAdmin/Admin/GET_INIT_DATA_SUCCEEDED';
export const GET_SECURED_DATA = 'StrapiAdmin/Admin/GET_SECURED_DATA';
export const GET_SECURED_DATA_SUCCEEDED =
'StrapiAdmin/Admin/GET_SECURED_DATA_SUCCEEDED';
export const HIDE_LEFT_MENU = 'StrapiAdmin/Admin/HIDE_LEFT_MENU';
export const HIDE_LOGOUT = 'StrapiAdmin/Admin/HIDE_LOGOUT';
export const SET_APP_ERROR = 'StrapiAdmin/Admin/SET_APP_ERROR';
export const SET_APP_SECURED = 'StrapiAdmin/Admin/SET_APP_SECURED';
export const SHOW_LEFT_MENU = 'StrapiAdmin/Admin/SHOW_LEFT_MENU';
export const SHOW_LOGOUT = 'StrapiAdmin/Admin/SHOW_LOGOUT';
export const UNSET_APP_SECURED = 'StrapiAdmin/Admin/UNSET_APP_SECURED';

View File

@ -1,385 +0,0 @@
/**
*
* Admin
*
*/
import React from 'react';
import ReactGA from 'react-ga';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { bindActionCreators, compose } from 'redux';
import { Switch, Route } from 'react-router-dom';
// Components from strapi-helper-plugin
import {
LoadingIndicatorPage,
OverlayBlocker,
injectHooks,
} from 'strapi-helper-plugin';
// import OverlayBlocker from 'components/OverlayBlocker';
// import injectHooks from 'utils/injectHooks';
import Header from '../../components/Header/index';
import Logout from '../../components/Logout';
import ComingSoonPage from '../ComingSoonPage';
import LeftMenu from '../LeftMenu';
import ListPluginsPage from '../ListPluginsPage';
import LocaleToggle from '../LocaleToggle';
import HomePage from '../HomePage';
import Marketplace from '../Marketplace';
import NotFoundPage from '../NotFoundPage';
import Onboarding from '../Onboarding';
import PluginDispatcher from '../PluginDispatcher';
import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
updatePlugin,
} from '../App/actions';
import makeSelecApp from '../App/selectors';
import injectSaga from '../../utils/injectSaga';
import injectReducer from '../../utils/injectReducer';
import localeToggleReducer from '../LocaleToggle/reducer';
import {
resetLocaleDefaultClassName,
setLocaleCustomClassName,
} from '../LocaleToggle/actions';
import {
emitEvent,
getInitData,
getSecuredData,
hideLeftMenu,
hideLogout,
setAppError,
setAppSecured,
showLeftMenu,
showLogout,
unsetAppSecured,
} from './actions';
import makeSelectAdmin from './selectors';
import reducer from './reducer';
import saga from './saga';
import NavTopRightWrapper from './NavTopRightWrapper';
import styles from './styles.scss';
export class Admin extends React.Component {
// eslint-disable-line react/prefer-stateless-function
state = { shouldSecureAfterAllPluginsAreMounted: true };
getChildContext = () => ({
emitEvent: this.props.emitEvent,
currentEnvironment: this.props.admin.currentEnvironment,
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
plugins: this.props.global.plugins,
updatePlugin: this.props.updatePlugin,
});
componentDidMount() {
// Initialize Google Analytics
// Refer to ../../../doc/disable-tracking.md for more informations
/* istanbul ignore next */
ReactGA.initialize('UA-54313258-9', {
testMode: process.env.NODE_ENV === 'test',
});
// Retrieve the main settings of the application
this.props.getInitData();
}
/* istanbul ignore next */
componentDidUpdate(prevProps) {
const {
admin: { didGetSecuredData, isLoading, isSecured },
getHook,
getSecuredData,
location: { pathname },
} = this.props;
if (!isLoading && this.state.shouldSecureAfterAllPluginsAreMounted) {
if (!this.hasApluginNotReady(this.props)) {
getHook('willSecure');
}
}
if (prevProps.location.pathname !== pathname) {
getHook('willSecure');
/* istanbul ignore if */
if (this.isAcceptingTracking()) {
ReactGA.pageview(pathname, {
testMode: process.env.NODE_ENV === 'test',
});
}
}
if (prevProps.admin.isSecured !== isSecured && isSecured) {
getSecuredData();
}
if (prevProps.admin.didGetSecuredData !== didGetSecuredData) {
getHook('didGetSecuredData');
}
}
/* istanbul ignore next */
componentDidCatch(error, info) {
/* eslint-disable */
console.log('An error has occured');
console.log('--------------------');
console.log(error);
console.log('Here is some infos');
console.log(info);
/* eslint-enable */
// Display the error log component which is not designed yet
this.props.setAppError();
}
getContentWrapperStyle = () => {
const {
admin: { showMenu },
} = this.props;
return showMenu
? { main: {}, sub: styles.content }
: { main: { width: '100%' }, sub: styles.wrapper };
};
hasApluginNotReady = props => {
const {
global: { plugins },
} = props;
return !Object.keys(plugins).every(
plugin => plugins[plugin].isReady === true,
);
};
helpers = {
hideLeftMenu: this.props.hideLeftMenu,
hideLogout: this.props.hideLogout,
setAppSecured: this.props.setAppSecured,
showLeftMenu: this.props.showLeftMenu,
showLogout: this.props.showLogout,
unsetAppSecured: this.props.unsetAppSecured,
updatePlugin: this.props.updatePlugin,
};
isAcceptingTracking = () => {
const {
admin: { uuid },
} = this.props;
return !!uuid;
};
/**
* Display the app loader until the app is ready
* @returns {Boolean}
*/
showLoader = () => {
const {
global: { isAppLoading },
} = this.props;
if (isAppLoading) {
return true;
}
return this.hasApluginNotReady(this.props);
};
renderInitializers = () => {
const {
global: { plugins },
} = this.props;
return Object.keys(plugins).reduce((acc, current) => {
const InitializerComponent = plugins[current].initializer;
const key = plugins[current].id;
acc.push(
<InitializerComponent key={key} {...this.props} {...this.helpers} />,
);
return acc;
}, []);
};
renderMarketPlace = props => <Marketplace {...props} {...this.props} />;
renderPluginDispatcher = props => {
// NOTE: Send the needed props instead of everything...
return <PluginDispatcher {...this.props} {...props} {...this.helpers} />;
};
render() {
const {
admin: { isLoading, showLogoutComponent, showMenu, strapiVersion },
global: { blockApp, overlayBlockerData, plugins, showGlobalAppBlocker },
} = this.props;
if (isLoading) {
return <LoadingIndicatorPage />;
}
// We need the admin data in order to make the initializers work
if (this.showLoader()) {
return (
<React.Fragment>
{this.renderInitializers()}
<LoadingIndicatorPage />
</React.Fragment>
);
}
return (
<div className={styles.adminPage}>
{showMenu && <LeftMenu version={strapiVersion} plugins={plugins} />}
<NavTopRightWrapper>
{/* Injection zone not ready yet */}
{showLogoutComponent && <Logout />}
<LocaleToggle isLogged />
</NavTopRightWrapper>
<div
className={styles.adminPageRightWrapper}
style={this.getContentWrapperStyle().main}
>
{showMenu ? <Header /> : ''}
<div className={this.getContentWrapperStyle().sub}>
<Switch>
<Route path="/" component={HomePage} exact />
<Route
path="/plugins/:pluginId"
render={this.renderPluginDispatcher}
/>
<Route path="/plugins" component={ComingSoonPage} />
<Route path="/list-plugins" component={ListPluginsPage} exact />
<Route
path="/marketplace"
render={this.renderMarketPlace}
exact
/>
<Route path="/configuration" component={ComingSoonPage} exact />
<Route key="7" path="" component={NotFoundPage} />
<Route key="8" path="404" component={NotFoundPage} />
</Switch>
</div>
</div>
<OverlayBlocker
key="overlayBlocker"
isOpen={blockApp && showGlobalAppBlocker}
{...overlayBlockerData}
/>
{showLogoutComponent && <Onboarding />}
</div>
);
}
}
Admin.childContextTypes = {
emitEvent: PropTypes.func,
currentEnvironment: PropTypes.string,
disableGlobalOverlayBlocker: PropTypes.func,
enableGlobalOverlayBlocker: PropTypes.func,
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};
Admin.propTypes = {
admin: PropTypes.shape({
autoReload: PropTypes.bool,
appError: PropTypes.bool,
currentEnvironment: PropTypes.string,
didGetSecuredData: PropTypes.bool,
isLoading: PropTypes.bool,
isSecured: PropTypes.bool,
layout: PropTypes.object,
showLogoutComponent: PropTypes.bool,
showMenu: PropTypes.bool,
strapiVersion: PropTypes.string,
uuid: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
}).isRequired,
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
emitEvent: PropTypes.func.isRequired,
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
getHook: PropTypes.func.isRequired,
getInitData: PropTypes.func.isRequired,
getSecuredData: PropTypes.func.isRequired,
global: PropTypes.shape({
appPlugins: PropTypes.array,
blockApp: PropTypes.bool,
overlayBlockerData: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
isAppLoading: PropTypes.bool,
plugins: PropTypes.object,
showGlobalAppBlocker: PropTypes.bool,
}).isRequired,
hideLeftMenu: PropTypes.func.isRequired,
hideLogout: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
resetLocaleDefaultClassName: PropTypes.func.isRequired,
setAppError: PropTypes.func.isRequired,
setAppSecured: PropTypes.func.isRequired,
showLeftMenu: PropTypes.func.isRequired,
showLogout: PropTypes.func.isRequired,
unsetAppSecured: PropTypes.func.isRequired,
updatePlugin: PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({
admin: makeSelectAdmin(),
global: makeSelecApp(),
});
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
disableGlobalOverlayBlocker,
emitEvent,
enableGlobalOverlayBlocker,
getInitData,
getSecuredData,
hideLeftMenu,
hideLogout,
resetLocaleDefaultClassName,
setAppError,
setAppSecured,
setLocaleCustomClassName,
showLeftMenu,
showLogout,
unsetAppSecured,
updatePlugin,
},
dispatch,
);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = injectReducer({ key: 'admin', reducer });
const withSaga = injectSaga({ key: 'admin', saga });
const withLocaleToggleReducer = injectReducer({
key: 'localeToggle',
reducer: localeToggleReducer,
});
const withHooks = injectHooks({ key: 'admin' });
export default compose(
withReducer,
withLocaleToggleReducer,
withSaga,
withConnect,
withHooks,
)(Admin);

View File

@ -1,75 +0,0 @@
/*
*
* Admin reducer
*
*/
import { fromJS, Map } from 'immutable';
import {
GET_INIT_DATA_SUCCEEDED,
GET_SECURED_DATA_SUCCEEDED,
HIDE_LEFT_MENU,
HIDE_LOGOUT,
SET_APP_ERROR,
SET_APP_SECURED,
SHOW_LEFT_MENU,
SHOW_LOGOUT,
UNSET_APP_SECURED,
} from './constants';
const initialState = fromJS({
autoReload: false,
appError: false,
currentEnvironment: 'development',
didGetSecuredData: false,
isLoading: true,
isSecured: false,
layout: Map({}),
// NOTE: This should be the models and our stuffs
// Since this api is not implemented yet I just set this vague key ATM
securedData: {},
showMenu: true,
showLogoutComponent: false,
strapiVersion: '3',
uuid: false,
});
function adminReducer(state = initialState, action) {
switch (action.type) {
case GET_INIT_DATA_SUCCEEDED: {
const {
data: { autoReload, currentEnvironment, layout, strapiVersion, uuid },
} = action;
return state
.update('autoReload', () => autoReload)
.update('currentEnvironment', () => currentEnvironment)
.update('isLoading', () => false)
.update('layout', () => Map(layout))
.update('strapiVersion', () => strapiVersion)
.update('uuid', () => uuid);
}
case GET_SECURED_DATA_SUCCEEDED:
return state
.update('didGetSecuredData', v => !v)
.update('securedData', () => action.data);
case HIDE_LEFT_MENU:
return state.update('showMenu', () => false);
case HIDE_LOGOUT:
return state.update('showLogoutComponent', () => false);
case SET_APP_ERROR:
return state.update('appError', () => true);
case SET_APP_SECURED:
return state.update('isSecured', () => true);
case SHOW_LEFT_MENU:
return state.update('showMenu', () => true);
case SHOW_LOGOUT:
return state.update('showLogoutComponent', () => true);
case UNSET_APP_SECURED:
return state.update('isSecured', () => false);
default:
return state;
}
}
export default adminReducer;

View File

@ -1,83 +0,0 @@
import { all, fork, call, put, select, takeLatest } from 'redux-saga/effects';
import { request } from 'strapi-helper-plugin';
import { getInitDataSucceeded, getSecuredDataSucceeded } from './actions';
import { EMIT_EVENT, GET_INIT_DATA, GET_SECURED_DATA } from './constants';
import { makeSelectUuid } from './selectors';
export function* emitter(action) {
try {
const requestURL = 'https://analytics.strapi.io/track';
const uuid = yield select(makeSelectUuid());
const { event, properties } = action;
if (uuid) {
yield call(
fetch, // eslint-disable-line no-undef
requestURL,
{
method: 'POST',
body: JSON.stringify({ event, uuid, properties }),
headers: {
'Content-Type': 'application/json',
},
},
);
}
} catch (err) {
console.log(err); // eslint-disable-line no-console
}
}
export function* getData() {
try {
const endPoints = [
'gaConfig',
'strapiVersion',
'currentEnvironment',
'layout',
];
const [
{ uuid },
{ strapiVersion },
{ autoReload, currentEnvironment },
{ layout },
] = yield all(
endPoints.map(endPoint =>
call(request, `/admin/${endPoint}`, { method: 'GET' }),
),
);
yield put(
getInitDataSucceeded({
autoReload,
uuid,
strapiVersion,
currentEnvironment,
layout,
}),
);
} catch (err) {
strapi.notification.error('notification.error');
}
}
/* istanbul ignore next */
export function* getSecuredData() {
try {
const data = {};
yield put(getSecuredDataSucceeded(data));
} catch (err) {
console.log(err); // eslint-disable-line no-console
}
}
// Individual exports for testing
export default function* defaultSaga() {
yield all([
fork(takeLatest, EMIT_EVENT, emitter),
fork(takeLatest, GET_INIT_DATA, getData),
fork(takeLatest, GET_SECURED_DATA, getSecuredData),
]);
}

View File

@ -1,29 +0,0 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the admin state domain
*/
const selectAdminDomain = () => state => state.get('admin');
/**
* Other specific selectors
*/
/**
* Default selector used by Admin
*/
const makeSelectAdmin = () =>
createSelector(
selectAdminDomain(),
substate => substate.toJS(),
);
const makeSelectUuid = () =>
createSelector(
selectAdminDomain(),
substate => substate.get('uuid'),
);
export default makeSelectAdmin;
export { makeSelectUuid, selectAdminDomain };

View File

@ -1,52 +0,0 @@
/* Import */
@import '../../styles/variables/variables';
.adminPage { /* stylelint-disable */
display: flex;
overflow-x: hidden;
}
.adminPageRightWrapper {
width: calc(100% - #{$left-menu-width});
}
.adminPageSeparator {
display: flex;
flex-direction: column;
justify-content: center;
height: 6rem;
}
.content { /* stylelint-ignore */
min-height: calc(100vh - #{$header-height});
width: calc(100vw - #{$left-menu-width});
margin-top: $header-height;
margin-left: $left-menu-width;
background: $content-background;
}
.wrapper { /* stylelint-ignore */
width: 100%;
min-height: 100%;
background: $content-background;
}
.localeDropdownMenuNotLogged{
background: transparent !important;
box-shadow: none !important;
border: 1px solid #e3e9f3 !important;
border-top: 0px !important;
button {
padding-left: 17px;
&:hover {
background-color: #F7F8F8 !important;
}
}
&:before{
box-shadow: none !important;
}
}

View File

@ -1,121 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getData Saga should call the strapi.notification action if the response errors 1`] = `
Object {
"@@redux-saga/IO": true,
"ALL": Array [
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/gaConfig",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/strapiVersion",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/currentEnvironment",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/layout",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
],
}
`;
exports[`getData Saga should dispatch the getInitDataSucceeded action if it requests the data successfully 1`] = `
Object {
"@@redux-saga/IO": true,
"ALL": Array [
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/gaConfig",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/strapiVersion",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/currentEnvironment",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
Object {
"@@redux-saga/IO": true,
"CALL": Object {
"args": Array [
"/admin/layout",
Object {
"method": "GET",
},
],
"context": null,
"fn": [Function],
},
},
],
}
`;

View File

@ -1,106 +0,0 @@
import {
emitEvent,
getInitData,
getInitDataSucceeded,
hideLeftMenu,
setAppError,
setAppSecured,
showLeftMenu,
unsetAppSecured,
} from '../actions';
import {
EMIT_EVENT,
GET_INIT_DATA,
GET_INIT_DATA_SUCCEEDED,
HIDE_LEFT_MENU,
SET_APP_ERROR,
SET_APP_SECURED,
SHOW_LEFT_MENU,
UNSET_APP_SECURED,
} from '../constants';
describe('<Admin /> actions', () => {
describe('EmitEvent', () => {
it('has a type EMIT_EVENT and returns the correct data', () => {
const expected = {
type: EMIT_EVENT,
event: 'test',
properties: {},
};
expect(emitEvent('test', {})).toEqual(expected);
});
});
describe('GetInitData Action', () => {
it('has a type of GET_INIT_DATA', () => {
const expected = {
type: GET_INIT_DATA,
};
expect(getInitData()).toEqual(expected);
});
});
describe('GetInitDataSucceeded Action', () => {
it('should return the correct type and the passed data', () => {
const data = { autoReload: true };
const expected = {
type: GET_INIT_DATA_SUCCEEDED,
data,
};
expect(getInitDataSucceeded(data)).toEqual(expected);
});
});
describe('HideLeftMenu Action', () => {
it('has a type of HIDE_LEFT_MENU', () => {
const expected = {
type: HIDE_LEFT_MENU,
};
expect(hideLeftMenu()).toEqual(expected);
});
});
describe('SetAppError Action', () => {
it('has a type of SET_APP_ERROR', () => {
const expected = {
type: SET_APP_ERROR,
};
expect(setAppError()).toEqual(expected);
});
});
describe('SetAppSecured Action', () => {
it('has a type of SET_APP_SECURED', () => {
const expected = {
type: SET_APP_SECURED,
};
expect(setAppSecured()).toEqual(expected);
});
});
describe('ShowLeftMenu Action', () => {
it('has a type of SHOW_LEFT_MENU', () => {
const expected = {
type: SHOW_LEFT_MENU,
};
expect(showLeftMenu()).toEqual(expected);
});
});
describe('UnsetAppSecured Action', () => {
it('has a type of UNSET_APP_SECURED', () => {
const expected = {
type: UNSET_APP_SECURED,
};
expect(unsetAppSecured()).toEqual(expected);
});
});
});

View File

@ -1,538 +0,0 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import { IntlProvider } from 'react-intl';
import { compose } from 'redux';
import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
updatePlugin,
} from '../../App/actions';
import { LoadingIndicatorPage, OverlayBlocker } from 'strapi-helper-plugin';
import injectReducer from '../../../utils/injectReducer';
import history from '../../../utils/history';
import configureStore from '../../../configureStore';
import messages from 'testUtils/commonTrads.json';
import Header from '../../../components/Header/index';
import Onboarding from '../../Onboarding';
import Logout from '../../../components/Logout';
import localeToggleReducer from '../../LocaleToggle/reducer';
import {
resetLocaleDefaultClassName,
setLocaleCustomClassName,
} from '../../LocaleToggle/actions';
import { Admin, mapDispatchToProps } from '../index';
import {
getInitData,
hideLeftMenu,
setAppError,
showLeftMenu,
} from '../actions';
import styles from '../styles.scss';
const initialState = {};
const store = configureStore(initialState, history);
const withLocaleToggleReducer = injectReducer({
key: 'localeToggle',
reducer: localeToggleReducer,
});
const WithAdmin = compose(withLocaleToggleReducer)(Admin);
const renderComponent = properties =>
mount(
React.createElement(
props => (
<Provider store={store}>
<IntlProvider locale="en" defaultLocale="en" messages={messages}>
<Router>
<WithAdmin store={store} {...props} />
</Router>
</IntlProvider>
</Provider>
),
properties,
),
);
describe('<Admin />, (with Redux), React lifecycle', () => {
let props;
beforeEach(() => {
props = {
admin: {
autoReload: false,
appError: false,
currentEnvironment: 'development',
didGetSecuredData: false,
isLoading: true,
isSecured: false,
layout: {},
showLogoutComponent: false,
showMenu: true,
securedData: {},
strapiVersion: '3',
uuid: false,
},
disableGlobalOverlayBlocker: jest.fn(),
emitEvent: jest.fn(),
enableGlobalOverlayBlocker: jest.fn(),
getInitData: jest.fn(),
getSecuredData: jest.fn(),
getHook: jest.fn(),
global: {
appPlugins: [],
blockApp: false,
overlayBlockerData: null,
hasUserPlugin: true,
isAppLoading: true,
plugins: {},
showGlobalAppBlocker: true,
},
localeToggle: {},
hideLeftMenu: jest.fn(),
hideLogout: jest.fn(),
location: {},
resetLocaleDefaultClassName: jest.fn(),
setAppError: jest.fn(),
setAppSecured: jest.fn(),
showLeftMenu: jest.fn(),
showLogout: jest.fn(),
showGlobalAppBlocker: jest.fn(),
unsetAppSecured: jest.fn(),
updatePlugin: jest.fn(),
};
});
it('should not crash when mounted', () => {
renderComponent(props);
});
describe('ComponentDidUpdate', () => {
it('should call the getSecuredData if the app is secured', () => {
const wrapper = renderComponent(props);
expect(props.getSecuredData).not.toHaveBeenCalled();
wrapper.setProps({
admin: { isSecured: true },
});
expect(props.getSecuredData).toHaveBeenCalled();
wrapper.unmount();
});
it('should call the getHook props with the willSecure arg if the pathname has changed', () => {
props.admin.isLoading = false;
props.global.isAppLoading = false;
const renderedComponent = renderComponent(props);
renderedComponent.setProps({
admin: { isLoading: false },
location: { pathname: '/admin/marketPlace' },
});
expect(props.getHook).toHaveBeenCalledWith('willSecure');
renderedComponent.unmount();
});
it('should emit the didGetSecured hook event when the secured data has been retrieved', () => {
const wrapper = renderComponent(props);
expect(props.getHook).not.toHaveBeenCalled();
wrapper.setProps({
admin: { didGetSecuredData: true },
});
expect(props.getHook).toHaveBeenCalledWith('didGetSecuredData');
wrapper.unmount();
});
});
});
describe('<Admin />', () => {
let props;
beforeEach(() => {
props = {
admin: {
autoReload: false,
appError: false,
currentEnvironment: 'development',
didGetSecuredData: false,
isLoading: true,
isSecured: false,
layout: {},
securedData: {},
showLogoutComponent: false,
showMenu: true,
strapiVersion: '3',
uuid: false,
},
disableGlobalOverlayBlocker: jest.fn(),
emitEvent: jest.fn(),
enableGlobalOverlayBlocker: jest.fn(),
getInitData: jest.fn(),
getHook: jest.fn(),
getSecuredData: jest.fn(),
global: {
appPlugins: [],
blockApp: false,
overlayBlockerData: null,
hasUserPlugin: true,
isAppLoading: true,
plugins: {},
showGlobalAppBlocker: true,
},
localeToggle: {},
hideLeftMenu: jest.fn(),
hideLogout: jest.fn(),
location: {},
resetLocaleDefaultClassName: jest.fn(),
setAppError: jest.fn(),
setAppSecured: jest.fn(),
showLeftMenu: jest.fn(),
showLogout: jest.fn(),
showGlobalAppBlocker: jest.fn(),
unsetAppSecured: jest.fn(),
updatePlugin: jest.fn(),
};
});
it('should not crash', () => {
shallow(<Admin {...props} />);
});
describe('render', () => {
it('should not display the header if the showMenu prop is false', () => {
const adminProps = Object.assign(props.admin, {
isLoading: false,
showMenu: false,
});
const renderedComponent = shallow(<Admin {...props} {...adminProps} />);
expect(renderedComponent.find(Header)).toHaveLength(0);
});
it('should not display the Logout if the showLogoutComponent prop is false', () => {
props.admin.isLoading = false;
props.global.isAppLoading = false;
const wrapper = shallow(<Admin {...props} />);
expect(wrapper.find(Logout)).toHaveLength(0);
expect(wrapper.find(Onboarding)).toHaveLength(0);
});
it('should display the Logout if the showLogoutComponent prop is true', () => {
props.admin.showLogoutComponent = true;
props.admin.isLoading = false;
props.global.isAppLoading = false;
const wrapper = shallow(<Admin {...props} />);
expect(wrapper.find(Logout)).toHaveLength(1);
expect(wrapper.find(Onboarding)).toHaveLength(1);
});
it('should display the OverlayBlocker if blockApp and showGlobalOverlayBlocker are true', () => {
const globalProps = Object.assign(props.global, {
blockApp: true,
isAppLoading: false,
});
props.admin.isLoading = false;
const renderedComponent = shallow(<Admin {...props} {...globalProps} />);
expect(renderedComponent.find(OverlayBlocker)).toHaveLength(1);
});
it('should display the LoadingIndicatorPage if the isLoading prop is true', () => {
const renderedComponent = shallow(<Admin {...props} />);
expect(renderedComponent.find(LoadingIndicatorPage)).toHaveLength(1);
});
});
describe('HasApluginNotReady instance', () => {
it('should return true if a plugin is not ready', () => {
props.global.plugins = {
test: { isReady: true },
other: { isReady: false },
};
const wrapper = shallow(<Admin {...props} />);
const { hasApluginNotReady } = wrapper.instance();
expect(hasApluginNotReady(props)).toBeTruthy();
});
it('should return false if all plugins are ready', () => {
props.global.plugins = {
test: { isReady: true },
other: { isReady: true },
};
const wrapper = shallow(<Admin {...props} />);
const { hasApluginNotReady } = wrapper.instance();
expect(hasApluginNotReady(props)).toBeFalsy();
});
});
describe('getContentWrapperStyle instance', () => {
it('should return an empty object for the main key if showMenu prop is true', () => {
const renderedComponent = shallow(<Admin {...props} />);
const { getContentWrapperStyle } = renderedComponent.instance();
const expected = { main: {}, sub: styles.content };
expect(getContentWrapperStyle()).toEqual(expected);
});
it('should not return an empty object for the main key if showMenu prop is true', () => {
const adminProps = Object.assign(props.admin, { showMenu: false });
const renderedComponent = shallow(<Admin {...props} {...adminProps} />);
const { getContentWrapperStyle } = renderedComponent.instance();
const expected = { main: { width: '100%' }, sub: styles.wrapper };
expect(getContentWrapperStyle()).toEqual(expected);
});
});
describe('isAcceptingTracking instance', () => {
it('should return false if the uuid prop is false', () => {
const renderedComponent = shallow(<Admin {...props} />);
const { isAcceptingTracking } = renderedComponent.instance();
expect(isAcceptingTracking()).toEqual(false);
});
it('should return false if the uuid prop is null', () => {
const adminProps = Object.assign(props.admin, { uuid: null });
const renderedComponent = shallow(<Admin {...props} {...adminProps} />);
const { isAcceptingTracking } = renderedComponent.instance();
expect(isAcceptingTracking()).toEqual(false);
});
it('should return true if the uuid prop is true', () => {
const adminProps = Object.assign(props.admin, { uuid: true });
const renderedComponent = shallow(<Admin {...props} {...adminProps} />);
const { isAcceptingTracking } = renderedComponent.instance();
expect(isAcceptingTracking()).toEqual(true);
});
it('should return true if the uuid prop is a string', () => {
const adminProps = Object.assign(props.admin, { uuid: 'uuid' });
const renderedComponent = shallow(<Admin {...props} {...adminProps} />);
const { isAcceptingTracking } = renderedComponent.instance();
expect(isAcceptingTracking()).toEqual(true);
});
});
describe('renderMarketPlace instance', () => {
it('should return the MarketPlace container', () => {
const renderedComponent = shallow(<Admin {...props} />);
const { renderMarketPlace } = renderedComponent.instance();
expect(renderMarketPlace()).not.toBeNull();
});
});
describe('renderInitializers', () => {
it('should render the plugins initializer components', () => {
const Initializer = () => <div>Initializer</div>;
props.admin.isLoading = false;
props.global.plugins = {
test: {
initializer: Initializer,
isReady: false,
id: 'test',
},
};
const wrapper = shallow(<Admin {...props} />);
expect(wrapper.find(Initializer)).toHaveLength(1);
});
});
describe('renderPluginDispatcher instance', () => {
it('should return the pluginDispatcher component', () => {
const renderedComponent = shallow(<Admin {...props} />);
const { renderPluginDispatcher } = renderedComponent.instance();
expect(renderPluginDispatcher()).not.toBeNull();
});
});
});
describe('<Admin />, mapDispatchToProps', () => {
describe('disableGlobalOverlayBlocker', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.disableGlobalOverlayBlocker).toBeDefined();
});
it('should dispatch the disableGlobalOverlayBlocker action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.disableGlobalOverlayBlocker();
expect(dispatch).toHaveBeenCalledWith(disableGlobalOverlayBlocker());
});
});
describe('enableGlobalOverlayBlocker', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.enableGlobalOverlayBlocker).toBeDefined();
});
it('should dispatch the enableGlobalOverlayBlocker action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.enableGlobalOverlayBlocker();
expect(dispatch).toHaveBeenCalledWith(enableGlobalOverlayBlocker());
});
});
describe('getInitData', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.getInitData).toBeDefined();
});
it('should dispatch the getInitData action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.getInitData();
expect(dispatch).toHaveBeenCalledWith(getInitData());
});
});
describe('hideLeftMenu', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.hideLeftMenu).toBeDefined();
});
it('should dispatch the hideLeftMenu action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.hideLeftMenu();
expect(dispatch).toHaveBeenCalledWith(hideLeftMenu());
});
});
describe('setAppError', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.setAppError).toBeDefined();
});
it('should dispatch the setAppError action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.setAppError();
expect(dispatch).toHaveBeenCalledWith(setAppError());
});
});
describe('showLeftMenu', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.showLeftMenu).toBeDefined();
});
it('should dispatch the showLeftMenu action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.showLeftMenu();
expect(dispatch).toHaveBeenCalledWith(showLeftMenu());
});
});
describe('resetLocaleDefaultClassName', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.resetLocaleDefaultClassName).toBeDefined();
});
it('should dispatch the resetLocaleDefaultClassName action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.resetLocaleDefaultClassName();
expect(dispatch).toHaveBeenCalledWith(resetLocaleDefaultClassName());
});
});
describe('setLocaleCustomClassName', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.setLocaleCustomClassName).toBeDefined();
});
it('should dispatch the setLocaleCustomClassName action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.setLocaleCustomClassName();
expect(dispatch).toHaveBeenCalledWith(setLocaleCustomClassName());
});
});
describe('updatePlugin', () => {
it('should be injected', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
expect(result.updatePlugin).toBeDefined();
});
it('should dispatch the updatePlugin action when called', () => {
const dispatch = jest.fn();
const result = mapDispatchToProps(dispatch);
result.updatePlugin();
expect(dispatch).toHaveBeenCalledWith(updatePlugin());
});
});
});

View File

@ -1,21 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import NavTopRightWrapper from '../NavTopRightWrapper';
describe('<NavTopRightWrapper />', () => {
it('should not crash', () => {
shallow(<NavTopRightWrapper />);
});
it('should render some child if given', () => {
const Child = () => <div>Child</div>;
const renderedComponent = shallow(
<NavTopRightWrapper>
<Child />
</NavTopRightWrapper>,
);
expect(renderedComponent.find(Child).exists()).toBe(true);
});
});

View File

@ -1,114 +0,0 @@
import { fromJS, Map } from 'immutable';
import {
getSecuredDataSucceeded,
getInitDataSucceeded,
hideLeftMenu,
hideLogout,
setAppError,
setAppSecured,
showLeftMenu,
showLogout,
unsetAppSecured,
} from '../actions';
import adminReducer from '../reducer';
describe('adminReducer', () => {
let state;
beforeEach(() => {
state = fromJS({
autoReload: false,
appError: false,
currentEnvironment: 'development',
didGetSecuredData: false,
isLoading: true,
isSecured: false,
layout: Map({}),
securedData: {},
showLogoutComponent: false,
showMenu: true,
strapiVersion: '3',
uuid: false,
});
});
it('returns the initial state', () => {
const expected = state;
expect(adminReducer(undefined, {})).toEqual(expected);
});
it('should handle the getSecuredDataSucceeded action correctly', () => {
const expected = state
.set('didGetSecuredData', true)
.set('securedData', undefined);
expect(adminReducer(state, getSecuredDataSucceeded())).toEqual(expected);
});
it('should handle the getAdminDataSucceeded action correctly', () => {
const data = {
autoReload: true,
currentEnvironment: 'production',
isLoading: false,
layout: {},
strapiVersion: '3.0.0-beta',
uuid: 'uuid',
};
const expected = state
.set('autoReload', true)
.set('currentEnvironment', 'production')
.set('isLoading', false)
.set('layout', Map({}))
.set('strapiVersion', '3.0.0-beta')
.set('uuid', 'uuid');
expect(adminReducer(state, getInitDataSucceeded(data))).toEqual(expected);
});
it('should handle the hideLeftMenu action correctly', () => {
const expected = state.set('showMenu', false);
expect(adminReducer(state, hideLeftMenu())).toEqual(expected);
});
it('should handle the hideLogout action correctly', () => {
const expected = state.set('showLogoutComponent', false);
expect(adminReducer(state, hideLogout())).toEqual(expected);
});
it('should handle the setaAppError action correctly', () => {
const expected = state.set('appError', true);
expect(adminReducer(state, setAppError())).toEqual(expected);
});
it('should handle the setAppSecured action correctly', () => {
const expected = state.set('isSecured', true);
expect(adminReducer(state, setAppSecured())).toEqual(expected);
});
it('should handle the showLeftMenu action correctly', () => {
const expected = state.set('showMenu', true);
state.set('showMenu', false);
expect(adminReducer(state, showLeftMenu())).toEqual(expected);
});
it('should handle the showLogout action correctly', () => {
const expected = state.set('showLogoutComponent', true);
state.set('showLogoutComponent', false);
expect(adminReducer(state, showLogout())).toEqual(expected);
});
it('should handle the unsetAppSecured action correctly', () => {
state.set('isSecured', true);
const expected = state.set('isSecured', false);
expect(adminReducer(state, unsetAppSecured())).toEqual(expected);
});
});

View File

@ -1,78 +0,0 @@
/**
* Test sagas
*/
/* eslint-disable redux-saga/yield-effects */
import { all, fork, put, takeLatest } from 'redux-saga/effects';
import defaultSaga, { emitter, getData, getSecuredData } from '../saga';
import { getInitDataSucceeded } from '../actions';
import { EMIT_EVENT, GET_INIT_DATA, GET_SECURED_DATA } from '../constants';
describe('getData Saga', () => {
let getDataGenerator;
beforeEach(() => {
getDataGenerator = getData();
const response = [
{ uuid: 'uuid' },
{ strapiVersion: 'beta' },
{ autoReload: true, currentEnvironment: 'test' },
{ layout: {} },
];
const callDescriptor = getDataGenerator.next(response).value;
expect(callDescriptor).toMatchSnapshot();
});
it('should dispatch the getInitDataSucceeded action if it requests the data successfully', () => {
const response = [
{ uuid: 'uuid' },
{ strapiVersion: 'beta' },
{ autoReload: true, currentEnvironment: 'test' },
{ layout: {} },
];
const putDescriptor = getDataGenerator.next(response).value;
const [
{ uuid },
{ strapiVersion },
{ autoReload, currentEnvironment },
{ layout },
] = response;
expect(putDescriptor).toEqual(
put(
getInitDataSucceeded({
autoReload,
uuid,
strapiVersion,
currentEnvironment,
layout,
}),
),
);
});
it('should call the strapi.notification action if the response errors', () => {
const response = new Error('Some error');
getDataGenerator.throw(response).value;
expect(strapi.notification.error).toHaveBeenCalled();
});
});
describe('defaultSaga Saga', () => {
const defaultSagaSaga = defaultSaga();
it('should start task to watch for GET_INIT_DATA and GET_SECURED_DATA actions', () => {
const forkDescriptor = defaultSagaSaga.next().value;
expect(forkDescriptor).toEqual(
all([
fork(takeLatest, EMIT_EVENT, emitter),
fork(takeLatest, GET_INIT_DATA, getData),
fork(takeLatest, GET_SECURED_DATA, getSecuredData),
]),
);
});
});

View File

@ -1,45 +0,0 @@
import { fromJS, Map } from 'immutable';
import makeSelectAdminDomain, { selectAdminDomain } from '../selectors';
describe('<Admin /> selectors', () => {
describe('selectAdminDomain selector', () => {
it('should select the global state', () => {
const state = fromJS({
autoReload: false,
appError: false,
currentEnvironment: 'development',
isLoading: true,
layout: Map({}),
showLeftMenu: true,
strapiVersion: '3',
uuid: false,
});
const mockedState = fromJS({
admin: state,
});
expect(selectAdminDomain()(mockedState)).toEqual(state);
});
});
describe('makeSelectAdminDomain', () => {
it('should select the global state (.toJS())', () => {
const state = fromJS({
autoReload: false,
appError: false,
currentEnvironment: 'development',
isLoading: true,
layout: Map({}),
showLeftMenu: true,
strapiVersion: '3',
uuid: false,
});
const mockedState = fromJS({
admin: state,
});
expect(makeSelectAdminDomain()(mockedState)).toEqual(state.toJS());
});
});
});

View File

@ -1,86 +0,0 @@
/*
*
* LanguageProvider actions
*
*/
import {
DISABLE_GLOBAL_OVERLAY_BLOCKER,
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
GET_APP_PLUGINS_SUCCEEDED,
LOAD_PLUGIN,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from './constants';
export function disableGlobalOverlayBlocker() {
return {
type: DISABLE_GLOBAL_OVERLAY_BLOCKER,
};
}
export function enableGlobalOverlayBlocker() {
return {
type: ENABLE_GLOBAL_OVERLAY_BLOCKER,
};
}
export function freezeApp(data) {
return {
type: FREEZE_APP,
data,
};
}
export function getAppPluginsSucceeded(plugins) {
return {
type: GET_APP_PLUGINS_SUCCEEDED,
appPlugins: plugins.map(plugin => plugin.id),
};
}
export function loadPlugin(newPlugin) {
return {
type: LOAD_PLUGIN,
plugin: newPlugin,
};
}
export function pluginDeleted(plugin) {
return {
type: PLUGIN_DELETED,
plugin,
};
}
export function pluginLoaded(newPlugin) {
return {
type: PLUGIN_LOADED,
plugin: newPlugin,
};
}
export function unfreezeApp() {
return {
type: UNFREEZE_APP,
};
}
export function unsetHasUserPlugin() {
return {
type: UNSET_HAS_USERS_PLUGIN,
};
}
export function updatePlugin(pluginId, updatedKey, updatedValue) {
return {
type: UPDATE_PLUGIN,
pluginId,
updatedKey,
updatedValue,
};
}

View File

@ -1,18 +0,0 @@
/*
*
* App constants
*
*/
export const FREEZE_APP = 'app/App/FREEZE_APP';
export const GET_APP_PLUGINS_SUCCEEDED = 'app/App/GET_APP_PLUGINS_SUCCEEDED';
export const LOAD_PLUGIN = 'app/App/LOAD_PLUGIN';
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
export const PLUGIN_DELETED = 'app/App/PLUGIN_DELETED';
export const UNFREEZE_APP = 'app/App/UNFREEZE_APP';
export const UNSET_HAS_USERS_PLUGIN = 'app/App/UNSET_HAS_USERS_PLUGIN';
export const UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
export const DISABLE_GLOBAL_OVERLAY_BLOCKER =
'app/App/OverlayBlocker/DISABLE_GLOBAL_OVERLAY_BLOCKER';
export const ENABLE_GLOBAL_OVERLAY_BLOCKER =
'app/App/OverlayBlocker/ENABLE_GLOBAL_OVERLAY_BLOCKER';

View File

@ -1,53 +0,0 @@
/**
*
* App.js
*
* This component is the skeleton around the actual pages, and should only
* contain code that should be seen on all pages. (e.g. navigation bar)
*
* NOTE: while this component should technically be a stateless functional
* component (SFC), hot reloading does not currently support SFCs. If hot
* reloading is not a neccessity for you then you can refactor it and remove
* the linting exception.
*/
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { LoadingIndicatorPage } from 'strapi-helper-plugin';
import Admin from '../Admin';
import NotFoundPage from '../NotFoundPage';
import NotificationProvider from '../NotificationProvider';
import AppLoader from '../AppLoader';
import styles from './styles.scss';
function App(props) {
return (
<div>
<NotificationProvider />
<AppLoader>
{({ shouldLoad }) => {
if (shouldLoad) {
return <LoadingIndicatorPage />;
}
return (
<div className={styles.container}>
<Switch>
<Route
path="/"
render={router => <Admin {...props} {...router} />}
/>
<Route path="" component={NotFoundPage} />
</Switch>
</div>
);
}}
</AppLoader>
</div>
);
}
App.propTypes = {};
export default App;

View File

@ -1,62 +0,0 @@
// Shared constants
import { fromJS, List } from 'immutable';
import {
DISABLE_GLOBAL_OVERLAY_BLOCKER,
ENABLE_GLOBAL_OVERLAY_BLOCKER,
FREEZE_APP,
GET_APP_PLUGINS_SUCCEEDED,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from './constants';
const initialState = fromJS({
appPlugins: List([]),
blockApp: false,
overlayBlockerData: null,
hasUserPlugin: true,
isAppLoading: true,
plugins: {},
showGlobalAppBlocker: true,
});
function appReducer(state = initialState, action) {
switch (action.type) {
case DISABLE_GLOBAL_OVERLAY_BLOCKER:
return state.set('showGlobalAppBlocker', false);
case ENABLE_GLOBAL_OVERLAY_BLOCKER:
return state.set('showGlobalAppBlocker', true);
case FREEZE_APP:
return state.set('blockApp', true).update('overlayBlockerData', () => {
if (action.data) {
return action.data;
}
return null;
});
case GET_APP_PLUGINS_SUCCEEDED:
return state
.update('appPlugins', () => List(action.appPlugins))
.update('isAppLoading', () => false);
case PLUGIN_LOADED:
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
case UPDATE_PLUGIN:
return state.setIn(
['plugins', action.pluginId, action.updatedKey],
fromJS(action.updatedValue),
);
case PLUGIN_DELETED:
return state.deleteIn(['plugins', action.plugin]);
case UNFREEZE_APP:
return state.set('blockApp', false).set('overlayBlockerData', null);
case UNSET_HAS_USERS_PLUGIN:
return state.set('hasUserPlugin', false);
default:
return state;
}
}
export default appReducer;

View File

@ -1,61 +0,0 @@
import { createSelector } from 'reselect';
/**
* Direct selector to the languageToggle state domain
*/
const selectApp = () => (state) => state.get('app');
/**
* Select the language locale
*/
const selectPlugins = () => createSelector(
selectApp(),
(appState) => appState.get('plugins')
);
const makeSelectApp = () => createSelector(
selectApp(),
appState => appState.toJS(),
);
const selectHasUserPlugin = () => createSelector(
selectApp(),
(appState) => appState.get('hasUserPlugin'),
);
const makeSelectShowGlobalAppBlocker = () => createSelector(
selectApp(),
(appState) => appState.get('showGlobalAppBlocker'),
);
const makeSelectBlockApp = () => createSelector(
selectApp(),
(appState) => appState.get('blockApp'),
);
const makeSelectOverlayBlockerProps = () => createSelector(
selectApp(),
(appState) => appState.get('overlayBlockerData'),
);
const makeSelectIsAppLoading = () => createSelector(
selectApp(),
appState => appState.get('isAppLoading'),
);
const makeSelectAppPlugins = () => createSelector(
selectApp(),
appState => appState.get('appPlugins').toJS(),
);
export default makeSelectApp;
export {
selectApp,
selectHasUserPlugin,
selectPlugins,
makeSelectAppPlugins,
makeSelectBlockApp,
makeSelectOverlayBlockerProps,
makeSelectIsAppLoading,
makeSelectShowGlobalAppBlocker,
};

View File

@ -1,11 +0,0 @@
/**
* styles.css
*
* App container styles
*/
@import "../../styles/variables/variables";
.container {
display: block;
}

View File

@ -1,25 +0,0 @@
li.header{
color: #101622 !important;
font-weight: 600;
letter-spacing: 0.07rem;
-webkit-font-smoothing: subpixel-antialiased;
}
li.chapter > a{
transition: background-color 0.15s ease;
-webkit-transition: background-color 0.15s ease;
}
li.chapter:hover > a{
background: #eee !important;
text-decoration: none !important;
}
.versions-select select{
height: 36px;
border: 1px solid #ccc;
}
.book-summary{
background-color: #f7f7f7 !important;
}

View File

@ -1,143 +0,0 @@
import {
FREEZE_APP,
GET_APP_PLUGINS_SUCCEEDED,
LOAD_PLUGIN,
PLUGIN_DELETED,
PLUGIN_LOADED,
UNFREEZE_APP,
UNSET_HAS_USERS_PLUGIN,
UPDATE_PLUGIN,
} from '../constants';
import {
freezeApp,
getAppPluginsSucceeded,
loadPlugin,
pluginDeleted,
pluginLoaded,
unfreezeApp,
unsetHasUserPlugin,
updatePlugin,
} from '../actions';
describe('<App /> actions', () => {
describe('freezeApp', () => {
it('should return the correct type and the passed data', () => {
const data = { strapi: 'isCool' };
const expected = {
type: FREEZE_APP,
data,
};
expect(freezeApp(data)).toEqual(expected);
});
});
describe('unfreezeApp', () => {
it('should return the correct type', () => {
const expected = {
type: UNFREEZE_APP,
};
expect(unfreezeApp()).toEqual(expected);
});
});
describe('getAppPluginsSucceeded', () => {
it('should return the correct type and an array containing the id of the plugins', () => {
const plugins = [
{
id: 'content-manager',
source: {
development: '/content-manager/main.js',
production: '/content-manager/main.js',
staging: '/content-manager/main.js',
},
},
{
id: 'content-type-builder',
source: {
development: '/content-type-builder/main.js',
production: '/content-type-builder/main.js',
staging: '/content-type-builder/main.js',
},
},
];
const expected = {
type: GET_APP_PLUGINS_SUCCEEDED,
appPlugins: ['content-manager', 'content-type-builder'],
};
expect(getAppPluginsSucceeded(plugins)).toEqual(expected);
});
});
describe('loadPlugin', () => {
it('should return the correct type and the passed data', () => {
const plugin = {
id: 'content-manager',
description: 'Manage your content',
};
const expected = {
type: LOAD_PLUGIN,
plugin,
};
expect(loadPlugin(plugin)).toEqual(expected);
});
});
describe('pluginLoaded', () => {
it('should return the correct type and the passed data', () => {
const plugin = {
id: 'content-manager',
description: 'Manage your content',
};
const expected = {
type: PLUGIN_LOADED,
plugin,
};
expect(pluginLoaded(plugin)).toEqual(expected);
});
});
describe('pluginDeleted', () => {
it('should return the correct type and the passed data', () => {
const plugin = 'content-manager';
const expected = {
type: PLUGIN_DELETED,
plugin,
};
expect(pluginDeleted(plugin)).toEqual(expected);
});
});
describe('unsetHasUserPlugin', () => {
it('should return the correct type', () => {
const expected = {
type: UNSET_HAS_USERS_PLUGIN,
};
expect(unsetHasUserPlugin()).toEqual(expected);
});
});
describe('updatePlugin', () => {
it('should return the correct type and the passed data', () => {
const pluginId = 'content-manager';
const updatedKey = 'isReady';
const updatedValue = true;
const expected = {
type: UPDATE_PLUGIN,
pluginId,
updatedKey,
updatedValue,
};
expect(updatePlugin(pluginId, updatedKey, updatedValue)).toEqual(
expected,
);
});
});
});

View File

@ -1,31 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { Route } from 'react-router-dom';
import AppLoader from '../../AppLoader';
import App from '../../App';
describe('<App />', () => {
it('should render the <AppLoader />', () => {
const renderedComponent = shallow(<App />);
expect(renderedComponent.find(AppLoader)).toHaveLength(1);
});
it('Should render the <Switch /> if the app is loading', () => {
const topComp = shallow(<App />);
const insideAppLoaderNotLoading = shallow(
topComp.find(AppLoader).prop('children')({ shouldLoad: false }),
);
expect(insideAppLoaderNotLoading.find(Route).length).toBe(2);
});
it('should not render the <Switch /> if the app is loading', () => {
const topComp = shallow(<App />);
const insideAppLoaderLoading = shallow(
topComp.find(AppLoader).prop('children')({ shouldLoad: true }),
);
expect(insideAppLoaderLoading.find(Route).length).toBe(0);
});
});

View File

@ -1,129 +0,0 @@
import { fromJS, List } from 'immutable';
import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
freezeApp,
getAppPluginsSucceeded,
pluginDeleted,
pluginLoaded,
unfreezeApp,
unsetHasUserPlugin,
updatePlugin,
} from '../actions';
import appReducer from '../reducer';
describe('<App /> reducer', () => {
let state;
beforeEach(() => {
state = fromJS({
appPlugins: List([]),
blockApp: false,
overlayBlockerData: null,
hasUserPlugin: true,
isAppLoading: true,
plugins: {},
showGlobalAppBlocker: true,
});
});
it('should return the initial state', () => {
const expectedResult = state;
expect(appReducer(undefined, {})).toEqual(expectedResult);
});
it('should handle the disableGlobalOverlayBlocker action correctly', () => {
const expectedResult = state.set('showGlobalAppBlocker', false);
expect(appReducer(state, disableGlobalOverlayBlocker())).toEqual(
expectedResult,
);
});
it('should handle the enableGlobalOverlayBlocker action correctly', () => {
state = state.set('showGlobalAppBlocker', false);
const expectedResult = state.set('showGlobalAppBlocker', true);
expect(appReducer(state, enableGlobalOverlayBlocker())).toEqual(
expectedResult,
);
});
it('should handle the freezeApp action correctly and set the overlayBlockerData key if passed data', () => {
const expectedResult = state
.set('blockApp', true)
.set('overlayBlockerData', { title: 'A title' });
expect(appReducer(state, freezeApp({ title: 'A title' }))).toEqual(
expectedResult,
);
});
it('should handle the freezeApp action correctly and NOT set the overlayBlockerData key if no passed data', () => {
const expectedResult = state.set('blockApp', true);
expect(appReducer(state, freezeApp())).toEqual(expectedResult);
});
it('should handle the getAppPluginsSucceeded action correclty', () => {
const plugins = [{ id: 'content-manager' }];
const expectedResult = state
.set('appPlugins', List(['content-manager']))
.set('isAppLoading', false);
expect(appReducer(state, getAppPluginsSucceeded(plugins))).toEqual(
expectedResult,
);
});
it('should handle the pluginLoaded action correclty', () => {
const plugin = {
id: 'content-manager',
description: 'Manage your content',
};
const expectedResult = state.setIn(
['plugins', 'content-manager'],
fromJS(plugin),
);
expect(appReducer(state, pluginLoaded(plugin))).toEqual(expectedResult);
});
it('should handle the updatePlugin action correclty', () => {
const plugin = { id: 'content-manager', isReady: false };
state = state.setIn(['plugins', 'content-manager'], fromJS(plugin));
const expectedResult = state.setIn(
['plugins', 'content-manager', 'isReady'],
true,
);
expect(
appReducer(state, updatePlugin('content-manager', 'isReady', true)),
).toEqual(expectedResult);
});
it('should handle the pluginDeleted action correclty', () => {
const plugin = { id: 'content-manager', isReady: false };
state = state.setIn(['plugins', 'content-manager'], fromJS(plugin));
const expectedResult = state.deleteIn(['plugins', 'content-manager']);
expect(appReducer(state, pluginDeleted('content-manager'))).toEqual(
expectedResult,
);
});
it('should handle the unfreezeApp action correclty', () => {
state = state
.set('blockApp', true)
.set('overlayBlockerData', { foo: 'bar' });
const expectedResult = state
.set('blockApp', false)
.set('overlayBlockerData', null);
expect(appReducer(state, unfreezeApp())).toEqual(expectedResult);
});
it('should handle the unsetHasUserPlugin action correclty', () => {
const expectedResult = state.set('hasUserPlugin', false);
expect(appReducer(state, unsetHasUserPlugin())).toEqual(expectedResult);
});
});

View File

@ -1,126 +0,0 @@
import { fromJS } from 'immutable';
import makeSelectApp, {
selectApp,
selectHasUserPlugin,
selectPlugins,
makeSelectAppPlugins,
makeSelectBlockApp,
makeSelectOverlayBlockerProps,
makeSelectIsAppLoading,
makeSelectShowGlobalAppBlocker,
} from '../selectors';
describe('<App /> selectors', () => {
describe('selectApp', () => {
it('should select the global state', () => {
const appState = fromJS({});
const mockedState = fromJS({
app: appState,
});
expect(selectApp()(mockedState)).toEqual(appState);
});
});
describe('makeSelectApp', () => {
it('should select the appState (.toJS())', () => {
const appState = fromJS({});
const mockedState = fromJS({
app: appState,
});
expect(makeSelectApp()(mockedState)).toEqual(appState.toJS());
});
});
describe('selectHasUserPlugin', () => {
it('should select the hasUserPlugin', () => {
const appState = fromJS({
hasUserPlugin: true,
});
const mockedState = fromJS({
app: appState,
});
expect(selectHasUserPlugin()(mockedState)).toEqual(true);
});
});
describe('selectPlugins', () => {
it('should select the plugins', () => {
const plugins = fromJS({ email: { isReady: true } });
const mockedState = fromJS({
app: {
plugins,
},
});
expect(selectPlugins()(mockedState)).toEqual(plugins);
});
});
describe('makeSelectAppPlugins', () => {
it('should select the appPlugins', () => {
const plugins = ['email'];
const mockedState = fromJS({
app: {
appPlugins: plugins,
},
});
expect(makeSelectAppPlugins()(mockedState)).toEqual(plugins);
});
});
describe('makeSelectBlockApp', () => {
it('should select the blockApp', () => {
const mockedState = fromJS({
app: {
blockApp: true,
},
});
expect(makeSelectBlockApp()(mockedState)).toEqual(true);
});
});
describe('makeSelectOverlayBlockerProps', () => {
it('should select the overlayBlockerData', () => {
const overlayBlockerData = fromJS({ title: 'title' });
const mockedState = fromJS({
app: {
overlayBlockerData,
},
});
expect(makeSelectOverlayBlockerProps()(mockedState)).toEqual(
overlayBlockerData,
);
});
});
describe('makeSelectIsAppLoading', () => {
it('should select the isAppLoading', () => {
const mockedState = fromJS({
app: {
isAppLoading: true,
},
});
expect(makeSelectIsAppLoading()(mockedState)).toEqual(true);
});
});
describe('makeSelectShowGlobalAppBlocker', () => {
it('should select the showGlobalAppBlocker', () => {
const mockedState = fromJS({
app: {
showGlobalAppBlocker: true,
},
});
expect(makeSelectShowGlobalAppBlocker()(mockedState)).toEqual(true);
});
});
});

View File

@ -1,37 +0,0 @@
/**
*
* AppLoader
*
*/
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import makeSelectApp from '../App/selectors';
export class AppLoader extends React.Component {
shouldLoad = () => {
const { appPlugins, plugins: mountedPlugins } = this.props;
return appPlugins.length !== Object.keys(mountedPlugins).length;
};
render() {
const { children } = this.props;
return children({ shouldLoad: this.shouldLoad() });
}
}
AppLoader.propTypes = {
appPlugins: PropTypes.array.isRequired,
children: PropTypes.func.isRequired,
plugins: PropTypes.object.isRequired,
};
const mapStateToProps = makeSelectApp();
export default connect(
mapStateToProps,
null,
)(AppLoader);

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