1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-25 18:56:28 +02:00

Merge pull request #201 from ascribe/modernize-build-system

Modernize build system
This commit is contained in:
Brett Sun 2016-06-10 10:26:24 +02:00 committed by GitHub
commit 6b1609e8d9
59 changed files with 1321 additions and 1188 deletions

27
.babelrc Normal file
View File

@ -0,0 +1,27 @@
{
'presets': ['react', 'es2015-no-commonjs'],
'plugins': [
'transform-object-assign',
'transform-object-rest-spread',
'transform-react-display-name',
[ 'transform-runtime', {
'polyfill': false,
'regenerator': false
} ]
],
'sourceMaps': true,
'env': {
'demo': {
'plugins': [
[ 'react-transform', {
'transforms': [{
'transform': 'react-transform-hmr',
'imports': ['react'],
'locals': ['module']
}]
} ]
]
}
}
}

16
.bootstraprc Normal file
View File

@ -0,0 +1,16 @@
---
bootstrapVersion: 3
# Phase this out, first with react-component's bootstrap overrides, and then nothing at all
preBootstrapCustomizations: ./sass/bootstrap/overrides
styleLoaders:
- style
- css?sourceMap
- postcss
- sass?sourceMap&output=expanded&precision=8
# For now, just include everything from Bootstrap
styles: true
scripts: false

View File

@ -1,3 +0,0 @@
SAUCE_USERNAME=ascribe
SAUCE_ACCESS_KEY=
SAUCE_DEFAULT_URL=

25
.env_template Normal file
View File

@ -0,0 +1,25 @@
# App settings
ONION_APP_VERSION=
ONION_API_URL=http://localhost.com:8000/api
ONION_BASE_PATH=
ONION_SERVER_URL=http://localhost.com:8000
ONION_PORT=4000
# Raven settings
RAVEN_DSN_URL=
# S3 settings
S3_ACCESS_KEY=
# Dev server configuration
ONION_DEV_HOST=localhost
ONION_DEV_PORT=3000
# Integration testing
SAUCE_USERNAME=ascribe
SAUCE_ACCESS_KEY=
SAUCE_DEFAULT_URL=

View File

@ -1,8 +1,5 @@
.
gulpfile.js
node_modules
build/*
dist/*
node_modules/*
js/**/__tests__
server.js
js/components/ascribe_uploader/vendor
js/components/ascribe_uploader/vendor/*

View File

@ -1,61 +0,0 @@
{
"parser": "babel-eslint",
"env": {
"browser": true,
"es6": true,
},
"rules": {
"new-cap": [2, {newIsCap: true, capIsNew: false}],
"quotes": [2, "single"],
"eol-last": [0],
"no-mixed-requires": [0],
"no-underscore-dangle": [0],
"global-strict": [2, "always"],
"no-trailing-spaces": [2, { skipBlankLines: true }],
"no-console": 0,
"camelcase": [2, {"properties": "never"}],
"react/display-name": 0,
"react/jsx-boolean-value": 1,
"react/jsx-no-undef": 1,
"react/jsx-quotes": 1,
"react/jsx-sort-prop-types": 0,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
"react/no-did-mount-set-state": [1, "allow-in-func"],
"react/no-did-update-set-state": 1,
"react/no-multi-comp": 0,
"react/no-unknown-property": 1,
"react/prop-types": 1,
"react/react-in-jsx-scope": 1,
"react/self-closing-comp": 1,
"react/sort-comp": 1,
"react/wrap-multilines": 1
},
"globals": {
"Intercom": true,
"fetch": true,
"require": true,
"File": true
},
"plugins": [
"react"
],
"ecmaFeatures": {
"jsx": 1,
"modules": 1,
"arrowFunctions",
"classes": 1,
"blockBindings": 1,
"defaultParams": 1,
"destructuring": 1,
"objectLiteralComputedProperties": 1,
"objectLiteralDuplicateProperties": 0,
"objectLiteralShorthandMethods": 1,
"objectLiteralShorthandProperties": 1,
"restParams": 1,
"spread": 1,
"superInFunctions": 1,
"templateStrings": 1
}
}

6
.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "ascribe",
"rules": {
"no-console": [1, { "allow": ["error", "logGlobal"] }]
}
}

10
.gitignore vendored
View File

@ -1,3 +1,4 @@
.DS_Store
lib-cov
*.seed
*.log
@ -8,22 +9,19 @@ lib-cov
*.gz
*.sublime-project
.idea
spool-project.sublime-project
*.sublime-workspace
*.sublime-workspace
webapp-dependencies.txt
pids
logs
results
.env
build/*
dist/*
gemini-coverage/*
gemini-report/*
test/gemini/screenshots/*
node_modules/*
.DS_Store
.env

View File

@ -170,12 +170,11 @@ A: Use `npm dedupe` to remove duplicates in npm. This might fix that you're not
Q: How can I use a local copy of SPOOL and Onion?
A: Easily by starting the your gulp process with the following command:
```
ONION_BASE_URL='/' ONION_SERVER_URL='http://localhost.com:8000/' gulp serve
ONION_SERVER_URL='http://localhost.com:8000/' gulp serve
```
Or, by adding these two your environment variables:
Or, by adding the local server url to your environment variables:
```
ONION_BASE_URL='/'
ONION_SERVER_URL='http://localhost.com:8000/'
```

View File

@ -1,207 +0,0 @@
'use strict';
require("harmonize")();
var gulp = require('gulp');
var template = require('gulp-template');
var gulpif = require('gulp-if');
var sourcemaps = require('gulp-sourcemaps');
var util = require('gulp-util');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var watchify = require('watchify');
var browserify = require('browserify');
var browserSync = require('browser-sync');
var babelify = require('babelify');
var notify = require('gulp-notify');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var _ = require('lodash');
var eslint = require('gulp-eslint');
var jest = require('jest-cli');
var argv = require('yargs').argv;
var server = require('./server.js').app;
var minifyCss = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var opn = require('opn');
var config = {
bootstrapDir: './node_modules/bootstrap-sass',
jestOptions: {
rootDir: 'js',
scriptPreprocessor: '../node_modules/babel-jest',
testFileExtensions: [
'es6',
'js'
],
unmockedModulePathPatterns: [
'<rootDir>/node_modules/react',
'<rootDir>/node_modules/react-tools'
],
moduleFileExtensions: [
'js',
'json',
'react',
'es6'
]
},
filesToWatch: [
'build/css/*.css',
'build/js/*.js'
]
};
var SERVER_URL = process.env.ONION_SERVER_URL || 'https://staging.ascribe.io/';
var constants = {
BASE_URL: (function () { var baseUrl = process.env.ONION_BASE_URL || '/'; return baseUrl + (baseUrl.match(/\/$/) ? '' : '/'); })(),
SERVER_URL: SERVER_URL,
API_ENDPOINT: SERVER_URL + 'api/' || 'https://staging.ascribe.io/api/',
DEBUG: !argv.production,
CREDENTIALS: 'ZGltaUBtYWlsaW5hdG9yLmNvbTowMDAwMDAwMDAw' // dimi@mailinator:0000000000
};
gulp.task('build', ['js:build', 'sass:build', 'copy'], function() {
});
gulp.task('js:build', function() {
bundle(false);
});
gulp.task('serve', ['browser-sync', 'run-server', 'sass:build', 'sass:watch', 'copy'], function() {
bundle(true);
// opens the browser window with the correct url, which is localhost.com
opn('http://www.localhost.com:3000');
});
gulp.task('jest', function(done) {
jest.runCLI({ config : config.jestOptions }, ".", function() {
done();
});
});
gulp.task('jest:watch', function(done) {
gulp.watch([ config.jestOptions.rootDir + "/**/*.js" ], [ 'jest' ]);
});
gulp.task('run-server', function() {
server.listen(4000);
});
gulp.task('browser-sync', function() {
browserSync({
files: config.filesToWatch,
proxy: 'http://localhost:4000',
port: 3000,
open: false, // does not open the browser-window anymore (handled manually)
ghostMode: false,
notify: false // stop showing the browsersync pop up
});
});
gulp.task('sass:build', function () {
gulp.src('./sass/**/main.scss')
.pipe(template(constants))
.pipe(gulpif(!argv.production, sourcemaps.init()))
.pipe(sass({
includePaths: [
config.bootstrapDir + '/assets/stylesheets'
]
}).on('error', sass.logError))
.pipe(gulpif(!argv.production, sourcemaps.write('./maps')))
// We need to set `advanced` to false, as it merges
// some of the styles wrongly
.pipe(gulpif(argv.production, minifyCss({
advanced: false
})))
.pipe(gulp.dest('./build/css'))
.pipe(browserSync.stream());
});
gulp.task('sass:watch', function () {
gulp.watch('./sass/**/*.scss', ['sass:build']);
});
gulp.task('copy', function () {
var staticAssets = [
'./fonts/**/*',
'./img/**/*'
];
gulp.src(staticAssets, {base: './'})
.pipe(gulp.dest('./build'));
gulp.src(config.bootstrapDir + '/assets/fonts/**/*')
.pipe(gulp.dest('./build/fonts'));
gulp.src('./index.html')
.pipe(template(constants))
.pipe(gulp.dest('./build'));
});
gulp.task('lint', function () {
return gulp.src(['js/**/*.js'])
// eslint() attaches the lint output to the eslint property
// of the file object so it can be used by other modules.
.pipe(eslint())
// eslint.format() outputs the lint results to the console.
// Alternatively use eslint.formatEach() (see Docs).
.pipe(eslint.format());
// To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failOnError last.
});
gulp.task('lint:watch', function () {
gulp.watch('js/**/*.js', ['lint']);
});
function bundle(watch) {
var bro;
if (watch) {
bro = watchify(browserify('./js/app.js',
// Assigning debug to have sourcemaps
_.assign(watchify.args, {
debug: true
})));
bro.on('update', function() {
rebundle(bro, true);
});
} else {
bro = browserify('./js/app.js', {
debug: true
});
}
bro.transform(babelify.configure({
compact: false
}));
function rebundle(bundler, watch) {
return bundler.bundle()
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(source('app.js'))
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(buffer())
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(!argv.production, sourcemaps.init({
loadMaps: true
}))) // loads map from browserify file
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(!argv.production, sourcemaps.write())) // writes .map file
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulpif(argv.production, uglify({
mangle: true
})))
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(gulp.dest('./build/js'))
.on('error', notify.onError('Error: <%= error.message %>'))
.pipe(browserSync.stream())
.on('error', notify.onError('Error: <%= error.message %>'));
}
return rebundle(bro);
}

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
@ -10,16 +10,7 @@
<title>ascribe</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="<%= BASE_URL %>static/css/main.css">
<% DEBUG && print('<link rel="stylesheet" href="' + BASE_URL + 'static/css/maps/main.css.map">') %>
<script>
window.BASE_URL = '<%= BASE_URL %>';
window.SERVER_URL = '<%= SERVER_URL %>';
window.API_ENDPOINT = '<%= API_ENDPOINT %>';
<% DEBUG && print('window.DEBUG = true'); %>
<% DEBUG && print('window.CREDENTIALS = \'' + CREDENTIALS + '\''); %>
</script>
<!-- Typekit gibson font -->
<script src="https://use.typekit.net/gma2yhj.js"></script>
<script>
@ -44,8 +35,5 @@
s.src='https://widget.intercom.io/widget/{app_id}';
var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()
</script>
<!-- actual app -->
<script src="<%= BASE_URL %>static/js/app.js"></script>
</body>
</html>

116
js/app.js
View File

@ -1,100 +1,90 @@
'use strict';
import 'babel/polyfill';
import 'core-js/es6';
import 'core-js/stage/4';
import 'classlist-polyfill';
import 'isomorphic-fetch';
import React from 'react';
import { Router, Redirect } from 'react-router';
import { Router } from 'react-router';
import AppResolver from './app_resolver';
import history from './history';
import fetch from 'isomorphic-fetch';
import ApiUrls from './constants/api_urls';
import AppConstants from './constants/application_constants';
import getRoutes from './routes';
import requests from './utils/requests';
import { updateApiUrls } from './constants/api_urls';
import { getDefaultSubdomainSettings, getSubdomainSettings } from './utils/constants_utils';
import { initLogging } from './utils/error_utils';
import { getSubdomain } from './utils/general_utils';
import requests from './utils/requests';
// FIXME: rename these event actions
import EventActions from './actions/event_actions';
// You can comment out the modules you don't need
// import DebugHandler from './third_party/debug_handler';
import FacebookHandler from './third_party/facebook_handler';
import GoogleAnalyticsHandler from './third_party/ga_handler';
import IntercomHandler from './third_party/intercom_handler';
import NotificationsHandler from './third_party/notifications_handler';
import RavenHandler from './third_party/raven_handler';
import './third_party/facebook_handler';
import './third_party/ga_handler';
import './third_party/intercom_handler';
import './third_party/notifications_handler';
import './third_party/raven_handler';
// Import global stylesheet
import '../sass/main.scss';
const AppGateway = {
start() {
try {
const subdomain = getSubdomain();
const settings = getSubdomainSettings(subdomain);
let subdomainSettings;
AppConstants.whitelabel = settings;
updateApiUrls(settings.type, subdomain);
this.load(settings);
} catch(err) {
// if there are no matching subdomains, we're routing
// to the default frontend
try {
subdomainSettings = getSubdomainSettings(getSubdomain());
} catch (err) {
// if there are no matching subdomains, we''ll route to the default frontend
console.logGlobal(err);
this.load(getDefaultSubdomainSettings());
subdomainSettings = getDefaultSubdomainSettings();
}
this.load(subdomainSettings);
},
load(settings) {
const { subdomain, type } = settings;
let redirectRoute = (<Redirect from="/" to="/collection" />);
if (subdomain) {
// Some whitelabels have landing pages so we should not automatically redirect from / to /collection.
// Only www and cc do not have a landing page.
if (subdomain !== 'cc') {
redirectRoute = null;
}
// Adds a client specific class to the body for whitelabel styling
window.document.body.classList.add('client--' + subdomain);
}
// Send the applicationWillBoot event to the third-party stores
EventActions.applicationWillBoot(settings);
// `history.listen` is called on every route change, which is perfect for
// us in that case.
// `history.listen` is called on every route change, which is perfect for routeDidChange
// events.
history.listen(EventActions.routeDidChange);
React.render((
<Router history={history}>
{redirectRoute}
{getRoutes(type, subdomain)}
</Router>
), document.getElementById('main'));
// Adds a client specific class to the body for whitelabel styling
window.document.body.classList.add(`client--${settings.subdomain}`);
// Send the applicationDidBoot event to the third-party stores
EventActions.applicationDidBoot(settings);
AppResolver
.resolve(settings)
.then(({ apiUrls, redirectRoute, routes }) => {
// Initialize api urls and defaults for outgoing requests
requests.defaults({
urlMap: apiUrls,
http: {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials: 'include'
}
});
React.render((
<Router history={history}>
{redirectRoute}
{routes}
</Router>
), document.getElementById('main'));
// Send the applicationDidBoot event to the third-party stores
EventActions.applicationDidBoot(settings);
});
}
};
// Initialize pre-start components
initLogging();
requests.defaults({
urlMap: ApiUrls,
http: {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials: 'include'
}
});
// And bootstrap app
AppGateway.start();

22
js/app_resolver.js Normal file
View File

@ -0,0 +1,22 @@
import React from 'react';
import { Redirect } from 'react-router';
import Routes from './routes';
import ApiUrls from './constants/api_urls';
function resolve({ subdomain, type }) {
if (type === 'wallet') {
return System.import('./components/whitelabel/wallet/wallet_app_resolver')
.then(({ default: WalletAppResolver }) => WalletAppResolver.resolve(subdomain));
} else {
return Promise.resolve({
apiUrls: ApiUrls,
redirectRoute: (<Redirect from="/" to="/collection" />),
routes: Routes
});
}
}
export default { resolve };

View File

@ -81,14 +81,14 @@ let FurtherDetailsFileuploader = React.createClass({
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
endpoint: `${AppConstants.serverUrl}/s3/delete`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
}}
isReadyForFormSubmission={isReadyForFormSubmission}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
url: `${AppConstants.serverUrl}/s3/key/`,
fileClass: 'otherdata',
pieceId: pieceId
}}
@ -96,7 +96,7 @@ let FurtherDetailsFileuploader = React.createClass({
onValidationFailed={onValidationFailed}
setIsUploadReady={setIsUploadReady}
session={{
endpoint: AppConstants.serverUrl + 'api/blob/otherdatas/fineuploader_session/',
endpoint: `${AppConstants.serverUrl}/api/blob/otherdatas/fineuploader_session/`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
},
@ -109,7 +109,7 @@ let FurtherDetailsFileuploader = React.createClass({
}
}}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
endpoint: `${AppConstants.serverUrl}/s3/signature/`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}

View File

@ -72,7 +72,7 @@ let CreateContractForm = React.createClass({
<InputFineUploader
submitFile={this.submitFile}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
url: `${AppConstants.serverUrl}/s3/key/`,
fileClass: 'contract'
}}
createBlobRoutine={{

View File

@ -162,7 +162,7 @@ let RegisterPieceForm = React.createClass({
<InputFineUploader
ref={ref => this.refs.digitalWorkFineUploader = ref}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
url: `${AppConstants.serverUrl}/s3/key/`,
fileClass: 'digitalwork'
}}
createBlobRoutine={{
@ -191,7 +191,7 @@ let RegisterPieceForm = React.createClass({
onValidationFailed={this.handleThumbnailValidationFailed}
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
url: `${AppConstants.serverUrl}/s3/key/`,
fileClass: 'thumbnail'
}}
validation={{

View File

@ -121,7 +121,7 @@ const InputFineUploader = React.createClass({
setWarning={setWarning}
showErrorPrompt={showErrorPrompt}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
endpoint: `${AppConstants.serverUrl}/s3/signature/`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
@ -129,7 +129,7 @@ const InputFineUploader = React.createClass({
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
endpoint: `${AppConstants.serverUrl}/s3/delete`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}

View File

@ -7,10 +7,15 @@ import Panel from 'react-bootstrap/lib/Panel';
import AppConstants from '../../constants/application_constants';
import audiojs from '../../third_party/imports/audiojs';
import shmui from '../../third_party/imports/shmui';
import videojs from '../../third_party/imports/videojs';
import { escapeHTML } from '../../utils/general_utils';
import { extractFileExtensionFromUrl } from '../../utils/file_utils';
import { InjectInHeadUtils } from '../../utils/inject_utils';
/**
* This is the component that implements display-specific functionality.
*
@ -57,12 +62,9 @@ let Image = React.createClass({
componentDidMount() {
if (this.props.url) {
InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl)
.then(() =>
Q.all([
InjectInHeadUtils.inject(AppConstants.shmui.cssUrl),
InjectInHeadUtils.inject(AppConstants.shmui.sdkUrl)
]).then(() => { window.jQuery('.shmui-ascribe').shmui(); }));
shmui
.importLib()
.then(() => window.jQuery('.shmui-ascribe').shmui());
}
},
@ -89,13 +91,9 @@ let Audio = React.createClass({
},
componentDidMount() {
InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl).then(this.ready);
},
ready() {
window.audiojs.events.ready(function() {
window.audiojs.createAll();
});
audiojs
.importLib()
.then(() => window.audiojs.events.ready(() => window.audiojs.createAll()));
},
render() {
@ -142,11 +140,10 @@ let Video = React.createClass({
},
componentDidMount() {
Q.all([
InjectInHeadUtils.inject(AppConstants.videojs.cssUrl),
InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl)])
.then(() => this.setState({libraryLoaded: true}))
.fail(() => this.setState({libraryLoaded: false}));
videojs
.importLib()
.then(() => this.setState({ libraryLoaded: true }))
.catch(() => this.setState({ libraryLoaded: false }));
},
shouldComponentUpdate(nextProps, nextState) {

View File

@ -56,10 +56,11 @@ export function AuthRedirect({ to, when }) {
* hence transitionTo cannot be used directly.
*
* While we're getting rid of `query.redirect` explicitly in the
* above `else if` statement, here it's sufficient to just call
* `baseUrl` + `redirectAuthenticated`, as it gets rid of queries as well.
* above `else if` statement, here it's sufficient to just set the
* location to `${baseUrl}/${redirectAuthenticated}`, as this will
* get rid of queries as well.
*/
window.location = AppConstants.baseUrl + redirectAuthenticated;
window.location = `${AppConstants.baseUrl}/${redirectAuthenticated}`;
return true;
}

View File

@ -62,7 +62,7 @@ let ContractSettingsUpdateButton = React.createClass({
ref='fineuploader'
fileInputElement={UploadButton({ showLabel: false })}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
url: `${AppConstants.serverUrl}/s3/key/`,
fileClass: 'contract'
}}
createBlobRoutine={{
@ -75,7 +75,7 @@ let ContractSettingsUpdateButton = React.createClass({
}}
setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
endpoint: `${AppConstants.serverUrl}/s3/signature/`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}
@ -83,7 +83,7 @@ let ContractSettingsUpdateButton = React.createClass({
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
endpoint: `${AppConstants.serverUrl}/s3/delete`,
customHeaders: {
'X-CSRFToken': getCookie(AppConstants.csrftoken)
}

View File

@ -1,7 +1,8 @@
'use strict';
import React from 'react/addons';
import fineUploader from 'fineUploader';
// FIXME: remove once using react-components
import fineUploader from 'exports?qq!./vendor/s3.fine-uploader';
import Q from 'q';
import S3Fetcher from '../../fetchers/s3_fetcher';

View File

@ -1,6 +1,7 @@
'use strict';
import fineUploader from 'fineUploader';
// FIXME: remove once using react-components
import fineUploader from 'exports?qq!./vendor/s3.fine-uploader';
import MimeTypes from '../../constants/mime_types';

View File

@ -6,27 +6,27 @@ import walletConstants from './wallet_application_constants';
function getWalletApiUrls(subdomain) {
if (subdomain === 'cyland') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
'pieces_list': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + '/' + subdomain + '/users/'
};
} else if (subdomain === 'ikonotv') {
return {
'pieces_list': walletConstants.walletApiEndpoint + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + subdomain + '/pieces/${piece_id}/',
'user': walletConstants.walletApiEndpoint + subdomain + '/users/'
'pieces_list': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + '/' + subdomain + '/pieces/${piece_id}/',
'user': walletConstants.walletApiEndpoint + '/' + subdomain + '/users/'
};
} else if (subdomain === 'lumenus' || subdomain === '23vivi' ||
subdomain === 'polline' || subdomain === 'artcity' ||
subdomain === 'demo' || subdomain === 'liquidgallery') {
return {
'editions_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/editions/',
'edition': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/editions/${bitcoin_id}/',
'pieces_list': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + 'markets/' + subdomain + '/users/'
'editions_list': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/editions/',
'edition': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/editions/${bitcoin_id}/',
'pieces_list': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/',
'piece': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/',
'piece_extradata': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/pieces/${piece_id}/extradata/',
'user': walletConstants.walletApiEndpoint + '/markets/' + subdomain + '/users/'
};
}
return {};

View File

@ -2,8 +2,8 @@
import AppConstants from '../../../../constants/application_constants';
let walletConstants = {
walletApiEndpoint: AppConstants.apiEndpoint + 'whitelabel/'
const walletConstants = {
walletApiEndpoint: `${AppConstants.apiEndpoint}/whitelabel`
};
export default walletConstants;
export default walletConstants;

View File

@ -0,0 +1,22 @@
import React from 'react';
import { Redirect } from 'react-router';
import getWalletApiUrls from './constants/wallet_api_urls';
import getWalletRoutes from './wallet_routes';
import { updateApiUrls } from '../../../constants/api_urls';
function resolve(subdomain) {
// Most whitelabels have landing pages so we should not automatically redirect from / to /collection.
// Only cc does not have a landing page.
const redirectRoute = subdomain === 'cc' ? (<Redirect from="/" to="/collection" />) : null;
return {
redirectRoute,
apiUrls: updateApiUrls(getWalletApiUrls(subdomain)),
routes: getWalletRoutes(subdomain)
};
}
export default { resolve };

View File

@ -1,5 +1,3 @@
'use strict';
import React from 'react';
import { Route, IndexRoute } from 'react-router';
@ -17,7 +15,6 @@ import EditionContainer from '../../../components/ascribe_detail/edition_contain
import SettingsContainer from '../../../components/ascribe_settings/settings_container';
import ContractSettings from '../../../components/ascribe_settings/contract_settings';
import ErrorNotFoundPage from '../../../components/error_not_found_page';
import Footer from '../../../components/footer.js';
import CCRegisterPiece from './components/cc/cc_register_piece';
@ -54,409 +51,407 @@ import WalletApp from './wallet_app';
import { getLangText } from '../../../utils/lang_utils';
let ROUTES = {
const ROUTES = {
'cyland': (
<Route path='/' component={WalletApp}>
<Route path="/" component={WalletApp}>
<IndexRoute
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(CylandLanding)} />
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(CylandLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CylandRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(CylandRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CylandPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(CylandPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
disableOn="noPieces" />
<Route
path='editions/:editionId'
path="editions/:editionId"
component={EditionContainer} />
<Route
path='coa_verify'
path="coa_verify"
component={CoaVerifyContainer} />
<Route
path='pieces/:pieceId'
path="pieces/:pieceId"
component={CylandPieceContainer} />
<Route
path='*'
path="*"
component={ErrorNotFoundPage} />
</Route>
),
'cc': (
<Route path='/' component={WalletApp}>
<Route path="/" component={WalletApp}>
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CCRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(CCRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')} />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(PieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
disableOn="noPieces" />
<Route
path='pieces/:pieceId'
path="pieces/:pieceId"
component={PieceContainer} />
<Route
path='editions/:editionId'
path="editions/:editionId"
component={EditionContainer} />
<Route
path='coa_verify'
path="coa_verify"
component={CoaVerifyContainer} />
<Route
path='*'
path="*"
component={ErrorNotFoundPage} />
</Route>
),
'ikonotv': (
<Route path='/' component={WalletApp}>
<Route path="/" component={WalletApp}>
<IndexRoute
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(IkonotvLanding)} />
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(IkonotvLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='request_loan'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SendContractAgreementForm)}
path="request_loan"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SendContractAgreementForm)}
headerTitle={getLangText('SEND NEW CONTRACT')}
aclName='acl_create_contractagreement' />
aclName="acl_create_contractagreement" />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(IkonotvRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(IkonotvPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
disableOn="noPieces" />
<Route
path='contract_notifications'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} />
path="contract_notifications"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(IkonotvContractNotifications)} />
<Route
path='pieces/:pieceId'
path="pieces/:pieceId"
component={IkonotvPieceContainer} />
<Route
path='editions/:editionId'
path="editions/:editionId"
component={EditionContainer} />
<Route
path='coa_verify'
path="coa_verify"
component={CoaVerifyContainer} />
<Route
path='*'
path="*"
component={ErrorNotFoundPage} />
</Route>
),
'lumenus': (
<Route path='/' component={WalletApp}>
<Route path="/" component={WalletApp}>
<IndexRoute
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LumenusLanding)} />
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LumenusLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
disableOn="noPieces" />
<Route
path='pieces/:pieceId'
path="pieces/:pieceId"
component={MarketPieceContainer} />
<Route
path='editions/:editionId'
path="editions/:editionId"
component={MarketEditionContainer} />
<Route
path='coa_verify'
path="coa_verify"
component={CoaVerifyContainer} />
<Route
path='*'
path="*"
component={ErrorNotFoundPage} />
</Route>
),
'23vivi': (
<Route path='/' component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(Vivi23Landing)} />
<Route path="/" component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(Vivi23Landing)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)}
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)}
footer={MarketFooter} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)}
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)}
footer={MarketFooter} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)}
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)}
footer={MarketFooter} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)}
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)}
footer={MarketFooter} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)}
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)}
footer={MarketFooter} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)}
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)}
footer={MarketFooter} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit'
aclName="acl_wallet_submit"
footer={MarketFooter} />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(Vivi23PieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(Vivi23PieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces'
disableOn="noPieces"
footer={MarketFooter} />
<Route
path='pieces/:pieceId'
path="pieces/:pieceId"
component={MarketPieceContainer}
footer={MarketFooter} />
<Route
path='editions/:editionId'
path="editions/:editionId"
component={MarketEditionContainer}
footer={MarketFooter} />
<Route
path='coa_verify'
path="coa_verify"
component={CoaVerifyContainer}
footer={MarketFooter} />
<Route
path='*'
path="*"
component={ErrorNotFoundPage}
footer={MarketFooter} />
</Route>
),
'polline': (
<Route path='/' component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PollineLanding)} />
<Route path="/" component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PollineLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
disableOn="noPieces" />
<Route path="pieces/:pieceId" component={MarketPieceContainer} />
<Route path="editions/:editionId" component={MarketEditionContainer} />
<Route path="verify" component={CoaVerifyContainer} />
<Route path="*" component={ErrorNotFoundPage} />
</Route>
),
'artcity': (
<Route path='/' component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(ArtcityLanding)} />
<Route path="/" component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(ArtcityLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
disableOn="noPieces" />
<Route path="pieces/:pieceId" component={MarketPieceContainer} />
<Route path="editions/:editionId" component={MarketEditionContainer} />
<Route path="verify" component={CoaVerifyContainer} />
<Route path="*" component={ErrorNotFoundPage} />
</Route>
),
'demo': (
<Route path='/' component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(MarketLanding)} />
<Route path="/" component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(MarketLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
disableOn="noPieces" />
<Route path="pieces/:pieceId" component={MarketPieceContainer} />
<Route path="editions/:editionId" component={MarketEditionContainer} />
<Route path="verify" component={CoaVerifyContainer} />
<Route path="*" component={ErrorNotFoundPage} />
</Route>
),
'liquidgallery': (
<Route path='/' component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(MarketLanding)} />
<Route path="/" component={WalletApp}>
<IndexRoute component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(MarketLanding)} />
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)} />
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/', when: 'loggedOut'}))(LogoutContainer)} />
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/', when: 'loggedOut' }))(LogoutContainer)} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)} />
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)} />
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)} />
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketRegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketRegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
aclName='acl_wallet_submit' />
aclName="acl_wallet_submit" />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(MarketPieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
disableOn="noPieces" />
<Route path="pieces/:pieceId" component={MarketPieceContainer} />
<Route path="editions/:editionId" component={MarketEditionContainer} />
<Route path="verify" component={CoaVerifyContainer} />
<Route path="*" component={ErrorNotFoundPage} />
</Route>
)
};
function getRoutes(commonRoutes, subdomain) {
if(subdomain in ROUTES) {
return ROUTES[subdomain];
export default function getWalletRoutes(subdomain) {
if (subdomain in ROUTES) {
return (ROUTES[subdomain]);
} else {
throw new Error('Subdomain wasn\'t specified in the wallet app.');
throw new Error("Subdomain wasn't specified in the wallet app.");
}
}
export default getRoutes;

View File

@ -1,88 +1,80 @@
'use strict';
import AppConstants from './application_constants';
import getWalletApiUrls from '../components/whitelabel/wallet/constants/wallet_api_urls';
import { update } from '../utils/general_utils';
let ApiUrls = {
'applications': AppConstants.apiEndpoint + 'applications/',
'application_token_refresh': AppConstants.apiEndpoint + 'applications/refresh_token/',
'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/',
'blob_otherdatas': AppConstants.apiEndpoint + 'blob/otherdatas/',
'blob_contracts': AppConstants.apiEndpoint + 'blob/contracts/',
'blob_thumbnails': AppConstants.apiEndpoint + 'blob/thumbnails/',
'coa': AppConstants.apiEndpoint + 'coa/${id}/',
'coa_create': AppConstants.apiEndpoint + 'coa/',
'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/editions/${edition_id}/',
'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below
'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/',
'licenses': AppConstants.apiEndpoint + 'ownership/licenses/',
'note_private_edition': AppConstants.apiEndpoint + 'note/private/editions/',
'note_private_piece': AppConstants.apiEndpoint + 'note/private/pieces/',
'note_public_edition': AppConstants.apiEndpoint + 'note/public/editions/',
'note_public_piece': AppConstants.apiEndpoint + 'note/public/pieces/',
'notification_piecelist': AppConstants.apiEndpoint + 'notifications/pieces/',
'notification_piece': AppConstants.apiEndpoint + 'notifications/pieces/${piece_id}/',
'notification_editionlist': AppConstants.apiEndpoint + 'notifications/editions/',
'notification_edition': AppConstants.apiEndpoint + 'notifications/editions/${edition_id}/',
'notification_contractagreementlist': AppConstants.apiEndpoint + 'notifications/contract_agreements/',
'ownership_contract_agreements': AppConstants.apiEndpoint + 'ownership/contract_agreements/',
'ownership_contract_agreements_confirm': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/accept/',
'ownership_contract_agreements_deny': AppConstants.apiEndpoint + 'ownership/contract_agreements/${contract_agreement_id}/reject/',
'ownership_consigns': AppConstants.apiEndpoint + 'ownership/consigns/',
'ownership_consigns_confirm': AppConstants.apiEndpoint + 'ownership/consigns/confirm/',
'ownership_consigns_deny': AppConstants.apiEndpoint + 'ownership/consigns/deny/',
'ownership_consigns_withdraw': AppConstants.apiEndpoint + 'ownership/consigns/withdraw/',
'ownership_loans_pieces': AppConstants.apiEndpoint + 'ownership/loans/pieces/',
'ownership_loans_pieces_confirm': AppConstants.apiEndpoint + 'ownership/loans/pieces/confirm/',
'ownership_loans_pieces_deny': AppConstants.apiEndpoint + 'ownership/loans/pieces/deny/',
'ownership_loans_pieces_request': AppConstants.apiEndpoint + 'ownership/loans/pieces/request/',
'ownership_loans_pieces_request_confirm': AppConstants.apiEndpoint + 'ownership/loans/pieces/request_confirm/',
'ownership_loans_pieces_request_deny': AppConstants.apiEndpoint + 'ownership/loans/pieces/request_deny/',
'ownership_loans_editions': AppConstants.apiEndpoint + 'ownership/loans/editions/',
'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/editions/confirm/',
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/editions/deny/',
'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/',
'ownership_shares_pieces': AppConstants.apiEndpoint + 'ownership/shares/pieces/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',
'ownership_unconsigns_deny': AppConstants.apiEndpoint + 'ownership/unconsigns/deny/',
'ownership_unconsigns_request': AppConstants.apiEndpoint + 'ownership/unconsigns/request/',
'ownership_contract': AppConstants.apiEndpoint + 'ownership/contracts/${contract_id}/',
'ownership_contract_list': AppConstants.apiEndpoint + 'ownership/contracts/',
'piece': AppConstants.apiEndpoint + 'pieces/${piece_id}/',
'piece_extradata': AppConstants.apiEndpoint + 'pieces/${piece_id}/extradata/',
'pieces_list': AppConstants.apiEndpoint + 'pieces/',
'piece_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/pieces/${piece_id}/',
'user': AppConstants.apiEndpoint + 'users/',
'users_login': AppConstants.apiEndpoint + 'users/login/',
'users_logout': AppConstants.apiEndpoint + 'users/logout/',
'users_password_reset': AppConstants.apiEndpoint + 'users/reset_password/',
'users_password_reset_request': AppConstants.apiEndpoint + 'users/request_reset_password/',
'users_signup': AppConstants.apiEndpoint + 'users/',
'users_username': AppConstants.apiEndpoint + 'users/username/',
'users_profile': AppConstants.apiEndpoint + 'users/profile/',
'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/',
'webhook': AppConstants.apiEndpoint + 'webhooks/${webhook_id}/',
'webhooks': AppConstants.apiEndpoint + 'webhooks/',
'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/',
'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/',
'delete_s3_file': AppConstants.serverUrl + 's3/delete/',
'sign_url_s3': AppConstants.serverUrl + 's3/sign_url/'
const ApiUrls = {
'applications': AppConstants.apiEndpoint + '/applications/',
'application_token_refresh': AppConstants.apiEndpoint + '/applications/refresh_token/',
'blob_digitalworks': AppConstants.apiEndpoint + '/blob/digitalworks/',
'blob_otherdatas': AppConstants.apiEndpoint + '/blob/otherdatas/',
'blob_contracts': AppConstants.apiEndpoint + '/blob/contracts/',
'blob_thumbnails': AppConstants.apiEndpoint + '/blob/thumbnails/',
'coa': AppConstants.apiEndpoint + '/coa/${id}/',
'coa_create': AppConstants.apiEndpoint + '/coa/',
'coa_verify': AppConstants.apiEndpoint + '/coa/verify_coa/',
'edition': AppConstants.apiEndpoint + '/editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + '/editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + '/ownership/shares/editions/${edition_id}/',
'editions': AppConstants.apiEndpoint + '/editions/', // this should be moved to the one below
'editions_list': AppConstants.apiEndpoint + '/pieces/${piece_id}/editions/',
'licenses': AppConstants.apiEndpoint + '/ownership/licenses/',
'note_private_edition': AppConstants.apiEndpoint + '/note/private/editions/',
'note_private_piece': AppConstants.apiEndpoint + '/note/private/pieces/',
'note_public_edition': AppConstants.apiEndpoint + '/note/public/editions/',
'note_public_piece': AppConstants.apiEndpoint + '/note/public/pieces/',
'notification_piecelist': AppConstants.apiEndpoint + '/notifications/pieces/',
'notification_piece': AppConstants.apiEndpoint + '/notifications/pieces/${piece_id}/',
'notification_editionlist': AppConstants.apiEndpoint + '/notifications/editions/',
'notification_edition': AppConstants.apiEndpoint + '/notifications/editions/${edition_id}/',
'notification_contractagreementlist': AppConstants.apiEndpoint + '/notifications/contract_agreements/',
'ownership_contract_agreements': AppConstants.apiEndpoint + '/ownership/contract_agreements/',
'ownership_contract_agreements_confirm': AppConstants.apiEndpoint + '/ownership/contract_agreements/${contract_agreement_id}/accept/',
'ownership_contract_agreements_deny': AppConstants.apiEndpoint + '/ownership/contract_agreements/${contract_agreement_id}/reject/',
'ownership_consigns': AppConstants.apiEndpoint + '/ownership/consigns/',
'ownership_consigns_confirm': AppConstants.apiEndpoint + '/ownership/consigns/confirm/',
'ownership_consigns_deny': AppConstants.apiEndpoint + '/ownership/consigns/deny/',
'ownership_consigns_withdraw': AppConstants.apiEndpoint + '/ownership/consigns/withdraw/',
'ownership_loans_pieces': AppConstants.apiEndpoint + '/ownership/loans/pieces/',
'ownership_loans_pieces_confirm': AppConstants.apiEndpoint + '/ownership/loans/pieces/confirm/',
'ownership_loans_pieces_deny': AppConstants.apiEndpoint + '/ownership/loans/pieces/deny/',
'ownership_loans_pieces_request': AppConstants.apiEndpoint + '/ownership/loans/pieces/request/',
'ownership_loans_pieces_request_confirm': AppConstants.apiEndpoint + '/ownership/loans/pieces/request_confirm/',
'ownership_loans_pieces_request_deny': AppConstants.apiEndpoint + '/ownership/loans/pieces/request_deny/',
'ownership_loans_editions': AppConstants.apiEndpoint + '/ownership/loans/editions/',
'ownership_loans_confirm': AppConstants.apiEndpoint + '/ownership/loans/editions/confirm/',
'ownership_loans_deny': AppConstants.apiEndpoint + '/ownership/loans/editions/deny/',
'ownership_shares_editions': AppConstants.apiEndpoint + '/ownership/shares/editions/',
'ownership_shares_pieces': AppConstants.apiEndpoint + '/ownership/shares/pieces/',
'ownership_transfers': AppConstants.apiEndpoint + '/ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + '/ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + '/ownership/unconsigns/',
'ownership_unconsigns_deny': AppConstants.apiEndpoint + '/ownership/unconsigns/deny/',
'ownership_unconsigns_request': AppConstants.apiEndpoint + '/ownership/unconsigns/request/',
'ownership_contract': AppConstants.apiEndpoint + '/ownership/contracts/${contract_id}/',
'ownership_contract_list': AppConstants.apiEndpoint + '/ownership/contracts/',
'piece': AppConstants.apiEndpoint + '/pieces/${piece_id}/',
'piece_extradata': AppConstants.apiEndpoint + '/pieces/${piece_id}/extradata/',
'pieces_list': AppConstants.apiEndpoint + '/pieces/',
'piece_remove_from_collection': AppConstants.apiEndpoint + '/ownership/shares/pieces/${piece_id}/',
'user': AppConstants.apiEndpoint + '/users/',
'users_login': AppConstants.apiEndpoint + '/users/login/',
'users_logout': AppConstants.apiEndpoint + '/users/logout/',
'users_password_reset': AppConstants.apiEndpoint + '/users/reset_password/',
'users_password_reset_request': AppConstants.apiEndpoint + '/users/request_reset_password/',
'users_signup': AppConstants.apiEndpoint + '/users/',
'users_username': AppConstants.apiEndpoint + '/users/username/',
'users_profile': AppConstants.apiEndpoint + '/users/profile/',
'wallet_settings': AppConstants.apiEndpoint + '/users/wallet_settings/',
'webhook': AppConstants.apiEndpoint + '/webhooks/${webhook_id}/',
'webhooks': AppConstants.apiEndpoint + '/webhooks/',
'webhooks_events': AppConstants.apiEndpoint + '/webhooks/events/',
'whitelabel_settings': AppConstants.apiEndpoint + '/whitelabel/settings/${subdomain}/',
'delete_s3_file': AppConstants.serverUrl + '/s3/delete/',
'sign_url_s3': AppConstants.serverUrl + '/s3/sign_url/'
};
export function updateApiUrls(type, subdomain) {
if (type === 'wallet') {
update(getWalletApiUrls(subdomain));
}
export function updateApiUrls(updatedApiUrls) {
return Object.assign(ApiUrls, updatedApiUrls);
}
export default ApiUrls;

View File

@ -1,19 +1,13 @@
'use strict';
//const baseUrl = 'http://localhost:8000/api/';
//FIXME: referring to a global variable in `window` is not
// super pro. What if we render stuff on the server?
// - super-bro - Senor Developer, 14th July 2015
//const baseUrl = window.BASE_URL;
const apiEndpoint = window.API_ENDPOINT;
const serverUrl = window.SERVER_URL;
const baseUrl = window.BASE_URL;
const apiEndpoint = process.env.API_URL;
const serverUrl = process.env.SERVER_URL;
const baseUrl = process.env.APP_BASE_PATH;
const constants = {
apiEndpoint,
serverUrl,
baseUrl,
serverUrl,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions',
'acl_loan', 'acl_loan_request', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer', 'acl_wallet_submit'],
@ -104,12 +98,8 @@ const constants = {
'jquery': {
'sdkUrl': 'https://code.jquery.com/jquery-2.1.4.min.js'
},
'shmui': {
'sdkUrl': baseUrl + 'static/thirdparty/shmui/jquery.shmui.js',
'cssUrl': baseUrl + 'static/thirdparty/shmui/shmui.css'
},
'audiojs': {
'sdkUrl': baseUrl + 'static/thirdparty/audiojs/audiojs/audio.min.js'
'sdkUrl': baseUrl + '/static/third_party/audiojs/audio.min.js'
},
'videojs': {
'sdkUrl': '//vjs.zencdn.net/4.12/video.js',

View File

@ -6,11 +6,8 @@ import createBrowserHistory from 'history/lib/createBrowserHistory';
import AppConstants from './constants/application_constants';
// Remove the trailing slash if present
const baseUrl = AppConstants.baseUrl.replace(/\/$/, '');
const history = useBasename(useQueries(createBrowserHistory))({
basename: baseUrl
basename: AppConstants.baseUrl
});
history.locationQueue = [];

View File

@ -1,10 +1,6 @@
'use strict';
import React from 'react';
import { Route } from 'react-router';
import getWalletRoutes from './components/whitelabel/wallet/wallet_routes';
import AscribeApp from './components/ascribe_app';
import PieceList from './components/piece_list';
@ -31,62 +27,56 @@ import { ProxyHandler, AuthRedirect } from './components/ascribe_routes/proxy_ha
import { getLangText } from './utils/lang_utils';
const COMMON_ROUTES = (
<Route path='/' component={AscribeApp}>
const Routes = (
<Route path="/" component={AscribeApp}>
<Route
path='login'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(LoginContainer)}
path="login"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(LoginContainer)}
footer={Footer} />
<Route
path='register_piece'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(RegisterPiece)}
path="register_piece"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(RegisterPiece)}
headerTitle={getLangText('+ NEW WORK')}
footer={Footer} />
<Route
path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)}
path="collection"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(PieceList)}
headerTitle={getLangText('COLLECTION')}
disableOn='noPieces'
disableOn="noPieces"
footer={Footer} />
<Route
path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)}
path="signup"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(SignupContainer)}
footer={Footer} />
<Route
path='logout'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(LogoutContainer)}
path="logout"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(LogoutContainer)}
footer={Footer} />
<Route path='pieces/:pieceId' component={PieceContainer}
<Route path="pieces/:pieceId" component={PieceContainer}
footer={Footer} />
<Route path='editions/:editionId' component={EditionContainer}
<Route path="editions/:editionId" component={EditionContainer}
footer={Footer} />
<Route
path='password_reset'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(PasswordResetContainer)}
path="password_reset"
component={ProxyHandler(AuthRedirect({ to: '/collection', when: 'loggedIn' }))(PasswordResetContainer)}
footer={Footer} />
<Route
path='settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SettingsContainer)}
path="settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(SettingsContainer)}
footer={Footer} />
<Route
path='contract_settings'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(ContractSettings)}
path="contract_settings"
component={ProxyHandler(AuthRedirect({ to: '/login', when: 'loggedOut' }))(ContractSettings)}
footer={Footer} />
<Route path='coa_verify' component={CoaVerifyContainer}
<Route
path="coa_verify"
component={CoaVerifyContainer}
footer={Footer} />
<Route path='*' component={ErrorNotFoundPage}
<Route
path="*"
component={ErrorNotFoundPage}
footer={Footer} />
</Route>
);
function getRoutes(type, subdomain) {
if (type === 'wallet') {
return getWalletRoutes(COMMON_ROUTES, subdomain);
} else {
return COMMON_ROUTES;
}
}
export default getRoutes;
export default Routes;

18
js/third_party/imports/audiojs.js vendored Normal file
View File

@ -0,0 +1,18 @@
import AppConstants from '../../constants/application_constants';
import { InjectInHeadUtils } from '../../utils/inject_utils';
/**
* Imports audiojs from the copied directory.
*
* Unfortunately, audiojs' package structure and the way it currently loads image assets prevents us
* from using System.import.
*
* @return {Promise} Promise that resolves with [audio.min.js] on success.
*/
function importLib() {
return InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl);
}
export default { importLib };

19
js/third_party/imports/shmui.js vendored Normal file
View File

@ -0,0 +1,19 @@
import AppConstants from '../../constants/application_constants';
import { InjectInHeadUtils } from '../../utils/inject_utils';
/**
* Imports shmui and its dependencies (jQuery)
*
* @return {Promise} Promise that resolves with [jquery.shmui.js, shmui.css] on success.
*/
function importLib() {
return InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl)
.then(() => Promise.all([
System.import('shmui/jquery.shmui'),
System.import('shmui/shmui.css')
]));
}
export default { importLib };

18
js/third_party/imports/videojs.js vendored Normal file
View File

@ -0,0 +1,18 @@
import AppConstants from '../../constants/application_constants';
import { InjectInHeadUtils } from '../../utils/inject_utils';
/**
* Imports videojs and its stylesheet.
*
* @return {Promise} Promise that resolves with [video.js, video-js.css] on success.
*/
function importLib() {
return Promise.all([
InjectInHeadUtils.inject(AppConstants.videojs.cssUrl),
InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl)
]);
}
export default { importLib };

View File

@ -1,21 +1,30 @@
{
"name": "Onion",
"version": "0.0.1",
"version": "0.0.2",
"description": "Das neue web client for Ascribe. Onions make you cry",
"homepapge": "https://www.ascribe.io",
"license": "Copyright",
"author": "Ascribe",
"main": "js/app.js",
"private": true,
"engines": {
"node": "0.10.x"
"node": "6.2.0"
},
"scripts": {
"lint": "eslint ./js",
"preinstall": "export SAUCE_CONNECT_DOWNLOAD_ON_INSTALL=true",
"postinstall": "npm run build",
"build": "gulp build --production",
"start": "node server.js",
"build": "rimraf ./dist && NODE_ENV=production webpack -p",
"start": "NODE_ENV=production node server.js",
"build:dev": "rimraf ./build && NODE_ENV=development webpack",
"build:extract": "rimraf ./build && NODE_ENV=extract webpack",
"clean": "rimraf ./build ./dist",
"start:dev": "NODE_ENV=development node server.dev.js",
"lint": "eslint ./",
"test": "npm run sauce-test",
"sauce-test": "mocha ./test/integration/tests/",
"tunnel": "node ./test/integration/tunnel.js",
"sauce-tunnel": "node ./test/integration/tunnel.js",
"vi-clean": "rm -rf ./gemini-report",
"vi-phantom": "phantomjs --webdriver=4444",
@ -31,78 +40,61 @@
"vi-test:lumenus": "npm run -s vi-test:base -- --browser LumenusDesktop --browser LumenusMobile",
"vi-test:23vivi": "npm run -s vi-test:base -- --browser 23viviDesktop --browser 23viviMobile"
},
"browser": {
"fineUploader": "./js/components/ascribe_uploader/vendor/s3.fine-uploader.js"
},
"browserify-shim": {
"fineUploader": "qq"
},
"browserify": {
"transform": [
[
"babelify",
{
"compact": false
}
],
"browserify-shim"
]
},
"author": "Ascribe",
"license": "Copyright",
"private": true,
"devDependencies": {
"babel-eslint": "^3.1.11",
"babel-jest": "^5.2.0",
"chai": "^3.4.1",
"chai-as-promised": "^5.1.0",
"colors": "^1.1.2",
"dotenv": "^1.2.0",
"gemini": "^2.1.0",
"gemini": "^4.3.0",
"jest-cli": "^0.4.0",
"mocha": "^2.3.4",
"phantomjs2": "^2.0.2",
"rimraf": "^2.5.2",
"sauce-connect-launcher": "^0.13.0",
"wd": "^0.4.0"
},
"dependencies": {
"alt": "^0.16.5",
"audiojs": "vrde/audiojs",
"babel": "^5.6.14",
"babelify": "^6.1.2",
"bootstrap-sass": "^3.3.4",
"browser-sync": "^2.7.5",
"browserify": "^9.0.8",
"browserify-shim": "^3.8.10",
"autoprefixer": "^6.3.6",
"babel-cli": "^6.9.0",
"babel-eslint": "^6.0.4",
"babel-loader": "^6.2.4",
"babel-plugin-react-transform": "^2.0.2",
"babel-plugin-transform-object-assign": "^6.8.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-plugin-transform-react-display-name": "^6.8.0",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-preset-es2015-no-commonjs": "0.0.2",
"babel-preset-react": "^6.5.0",
"babel-runtime": "^6.9.0",
"bootstrap-loader": "^1.0.10",
"bootstrap-sass": "^3.3.6",
"camelcase": "^1.2.1",
"classlist-polyfill": "^1.0.2",
"classnames": "^1.2.2",
"compression": "^1.4.4",
"compression": "^1.6.2",
"copy-webpack-plugin": "^3.0.1",
"core-js": "^2.4.0",
"css-loader": "^0.23.1",
"decamelize": "^1.1.1",
"envify": "^3.4.0",
"eslint": "^0.22.1",
"eslint-plugin-react": "^2.5.0",
"express": "^4.12.4",
"gulp": "^3.8.11",
"gulp-concat": "^2.5.2",
"gulp-eslint": "^0.13.2",
"gulp-if": "^1.2.5",
"gulp-minify-css": "^1.1.6",
"gulp-notify": "^2.2.0",
"gulp-sass": "^2.1.1",
"gulp-sourcemaps": "^1.5.2",
"gulp-template": "~3.0.0",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"harmonize": "^1.4.2",
"dotenv": "^1.2.0",
"eslint": "^2.11.1",
"eslint-config-ascribe": "^1.0.1",
"eslint-plugin-import": "^1.8.1",
"eslint-plugin-jsx-a11y": "^1.2.2",
"eslint-plugin-react": "^5.1.1",
"exports-loader": "^0.6.3",
"express": "^4.13.4",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"history": "1.17.0",
"html-webpack-plugin": "^2.19.0",
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.0.2",
"jest-cli": "^0.4.0",
"lodash": "^3.9.3",
"moment": "^2.10.6",
"object-assign": "^2.0.0",
"opn": "^3.0.2",
"node-sass": "^3.7.0",
"postcss-loader": "^0.9.1",
"q": "^1.4.1",
"query-string": "^3.0.0",
"raven-js": "^1.1.19",
@ -113,15 +105,18 @@
"react-router-bootstrap": "^0.19.0",
"react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.5.2",
"reactify": "^1.1.0",
"react-transform-hmr": "^1.0.4",
"remove-trailing-slash": "^0.1.0",
"resolve-url-loader": "^1.4.3",
"sass-loader": "^3.2.0",
"shallow-equals": "0.0.0",
"shmui": "^0.1.0",
"spark-md5": "~1.0.0",
"uglifyjs": "^2.4.10",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.1.2",
"yargs": "^3.10.0"
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^2.1.0-beta.7",
"webpack-combine-loaders": "^2.0.0",
"webpack-dev-server": "^2.1.0-beta.0"
},
"jest": {
"scriptPreprocessor": "node_modules/babel-jest",

View File

@ -13,11 +13,11 @@
@font-face {
font-family: 'ascribe-font';
src:url('#{$BASE_URL}static/fonts/ascribe-font.eot?q6qoae');
src:url('#{$BASE_URL}static/fonts/ascribe-font.eot?q6qoae#iefix') format('embedded-opentype'),
url('#{$BASE_URL}static/fonts/ascribe-font.ttf?q6qoae') format('truetype'),
url('#{$BASE_URL}static/fonts/ascribe-font.woff?q6qoae') format('woff'),
url('#{$BASE_URL}static/fonts/ascribe-font.svg?q6qoae#ascribe-font') format('svg');
src:url('../fonts/ascribe-font.eot?q6qoae');
src:url('../fonts/ascribe-font.eot?q6qoae#iefix') format('embedded-opentype'),
url('../fonts/ascribe-font.ttf?q6qoae') format('truetype'),
url('../fonts/ascribe-font.woff?q6qoae') format('woff'),
url('../fonts/ascribe-font.svg?q6qoae#ascribe-font') format('svg');
font-weight: normal;
font-style: normal;
}

View File

@ -0,0 +1,2 @@
@import '../ascribe_variables';
@import '../variables';

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,9 @@
// If you import a new .scss file, make sure to restart gulp
// otherwise it will not be included
$BASE_URL: '<%= BASE_URL %>';
@import 'mixins';
@import 'ascribe_variables';
@import 'variables';
@import '../node_modules/bootstrap-sass/assets/stylesheets/bootstrap';
@import '../node_modules/react-star-rating/dist/css/react-star-rating.min';
@import '../node_modules/react-datepicker/dist/react-datepicker';
@import 'glyphicons-social';

44
server.dev.js Normal file
View File

@ -0,0 +1,44 @@
/* eslint-disable strict, no-console */
/* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */
'use strict';
const removeTrailingSlash = require('remove-trailing-slash');
const WebpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const config = require('./webpack.config.js');
require('dotenv').load({ silent: true });
const HOST = process.env.ONION_DEV_HOST || 'localhost';
const PORT = process.env.ONION_DEV_PORT || 3000;
const BASE_PATH = removeTrailingSlash(process.env.ONION_BASE_PATH || '/');
// Enable server hot reloading
// React hot reloading is enabled through .babelrc and babel-react-transform
config.entry.unshift(`webpack-dev-server/client?http://${HOST}:${PORT}/`,
'webpack/hot/dev-server');
config.plugins.push(new webpack.HotModuleReplacementPlugin());
// Configure server
const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
publicPath: config.output.publicPath,
historyApiFallback: {
index: config.output.publicPath
},
hot: true,
noInfo: true,
stats: { colors: true }
});
// Start server
server.listen(PORT, HOST, (err) => {
if (err) {
console.error(`Onion dev server ran into ${err} while starting on ${HOST}:${PORT} ` +
`with basePath set to ${BASE_PATH || '/'}. Shutting down...`);
server.close();
}
console.log(`Onion server running on ${HOST}:${PORT} with basePath set to ${BASE_PATH || '/'}.`);
});

View File

@ -1,30 +1,28 @@
var argv = require('yargs').argv;
var express = require('express');
var compression = require('compression');
/* eslint-disable strict, no-console */
'use strict';
var baseUrl = (function () { var baseUrl = process.env.ONION_BASE_URL || '/'; return baseUrl + (baseUrl.match(/\/$/) ? '' : '/'); })();
const path = require('path');
const express = require('express');
const compression = require('compression');
const removeTrailingSlash = require('remove-trailing-slash');
var app = express();
const BASE_PATH = removeTrailingSlash(process.env.ONION_BASE_PATH || '/');
const PORT = process.env.ONION_PORT || 4000;
const app = express();
app.use(compression());
app.use(path.resolve(BASE_PATH, '/static'), express.static(path.resolve(__dirname, 'dist')));
app.use(baseUrl + 'static/js', express.static(__dirname + '/build/js'));
app.use(baseUrl + 'static/img', express.static(__dirname + '/build/img'));
app.use(baseUrl + 'static/css', express.static(__dirname + '/build/css'));
app.use(baseUrl + 'static/fonts', express.static(__dirname + '/build/fonts'));
app.use(baseUrl + 'static/thirdparty', express.static(__dirname + '/node_modules'));
app.get(/.*/, function(req, res) {
app.get(/.*/, (req, res) => {
console.log('%s %s', req.method, req.path);
res.sendFile(__dirname + '/build/index.html');
res.sendFile(path.resolve(__dirname, 'dist/index.html'));
});
if (require.main === module) {
var port = process.env.PORT || 4000;
console.log('Starting Onion server on port', port,
'baseUrl is set to', baseUrl);
app.listen(port);
console.log(`Starting Onion server on port ${PORT} with basePath set to ${BASE_PATH || '/'}`);
app.listen(PORT);
}
module.exports.app = app;

View File

@ -1,36 +0,0 @@
{
"parser": "babel-eslint",
"env": {
"mocha": true,
"node": true
},
"rules": {
"new-cap": [2, {newIsCap: true, capIsNew: false}],
"quotes": [2, "single"],
"eol-last": [0],
"no-mixed-requires": [0],
"no-underscore-dangle": [0],
"global-strict": [2, "always"],
"no-trailing-spaces": [2, { skipBlankLines: true }],
"no-console": 0,
"camelcase": [2, {"properties": "never"}],
},
"globals": {},
"plugins": [],
"ecmaFeatures": {
"modules": 1,
"arrowFunctions",
"classes": 1,
"blockBindings": 1,
"defaultParams": 1,
"destructuring": 1,
"objectLiteralComputedProperties": 1,
"objectLiteralDuplicateProperties": 0,
"objectLiteralShorthandMethods": 1,
"objectLiteralShorthandProperties": 1,
"restParams": 1,
"spread": 1,
"superInFunctions": 1,
"templateStrings": 1
}
}

13
test/.eslintrc.json Normal file
View File

@ -0,0 +1,13 @@
{
"env": {
"node": true
},
"rules": {
"func-names": [0],
"no-console": [0],
"no-process-exit": [0],
"strict": [0],
"import/newline-after-import": [0],
"import/no-extraneous-dependencies": [2, { "devDependencies": true, "optionalDependencies": false }]
}
}

View File

@ -0,0 +1,8 @@
{
"globals": {
"gemini": true
},
"rules": {
"max-len": [2, { "code": 125 }]
}
}

View File

@ -23,30 +23,41 @@ Then, install [PhantomJS2](https://www.npmjs.com/package/phantomjs2):
```bash
# Until phantomjs2 is updated for the new 2.1 version of PhantomJS, use the following (go to https://bitbucket.org/ariya/phantomjs/downloads to find a build for your OS)
npm install -g phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip
npm install --save-dev phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip
npm install phantomjs2 --phantomjs_downloadurl=https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-macosx.zip
# If using OSX, you may have to install upx and decompress the binary downloaded by npm manually:
brew install upx
# Navigate to the binary, ie. /Users/Brett/.nvm/versions/node/v5.4.0/lib/node_modules/phantomjs2/lib/phantom/bin/phantomjs
# Navigate to the binary, ie. node_modules/phantomjs2/lib/phantom/bin/ (for the local installation)
upx -d phantomjs
# If using Linux or OSX, you may also have to convert the binary using `dos2unix`:
brew install dos2unix
dos2unix phantomjs
```
Finally, [install Gemini globally and locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation).
Finally, [install Gemini locally with npm](https://github.com/gemini-testing/gemini/blob/master/README.md#installation), if it
hasn't already been installed through `package.json`.
Running Tests
=============
Run Spool and Onion (on localhost.com:3000).
Run PhantomJS:
```bash
npm run vi-phantom
```
And then run Gemini tests:
Gather your initial baseline (on the master branch, for example):
```bash
npm run vi-update
```
And then run Gemini tests (on your current branch with changes, to test for regressions):
```bash
npm run vi-test
@ -61,8 +72,8 @@ npm run vi-test:whitelabel
npm run vi-test:cyland
```
If you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test there are
no regressions first!), use
Later on, if you've made changes and want them to be the new baseline (ie. it's a correct change--**make sure** to test
there are no regressions first!), use:
```bash
npm run vi-update

View File

@ -1,3 +1,4 @@
/* eslint-disable strict, no-console */
'use strict';
const MAIN_USER = {

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../environment');
const MAIN_USER = environment.MAIN_USER;
const TIMEOUTS = environment.TIMEOUTS;
@ -12,7 +11,7 @@ gemini.suite('Authenticated', (suite) => {
suite
.setUrl('/collection')
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
@ -30,185 +29,191 @@ gemini.suite('Authenticated', (suite) => {
actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password);
actions.click(find('.ascribe-login-wrapper button[type=submit]'));
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG);
});
});
gemini.suite('Header-desktop', (headerSuite) => {
headerSuite
.setCaptureElements('nav.navbar .container')
// Ignore Cyland's logo as it's a gif
.ignoreElements('.client--cyland img.img-brand')
.skip(/Mobile/)
.before((actions, find) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
})
.capture('desktop header');
gemini.suite('Authenticated', (authenticatedSuite) => {
gemini.suite('Header-desktop', (headerSuite) => {
headerSuite
.setCaptureElements('nav.navbar .container')
// Ignore Cyland's logo as it's a gif
.ignoreElements('.client--cyland img.img-brand')
.skip(/Mobile/)
.before((actions) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG);
actions.waitForElementToShow('#header-notification-dropdown', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
})
.capture('desktop header');
gemini.suite('User dropdown', (headerUserSuite) => {
headerUserSuite
.setCaptureElements('#nav-route-user-dropdown ~ .dropdown-menu')
.capture('expanded user dropdown', (actions, find) => {
actions.click(find('#nav-route-user-dropdown'));
});
gemini.suite('User dropdown', (headerUserSuite) => {
headerUserSuite
.setCaptureElements('#nav-route-user-dropdown ~ .dropdown-menu')
.capture('expanded user dropdown', (actions, find) => {
actions.click(find('#nav-route-user-dropdown'));
});
});
gemini.suite('Notification dropdown', (headerNotificationSuite) => {
headerNotificationSuite
.setCaptureElements('#header-notification-dropdown ~ .dropdown-menu')
.capture('expanded notifications dropdown', (actions, find) => {
actions.click(find('#header-notification-dropdown'));
});
});
});
gemini.suite('Notification dropdown', (headerNotificationSuite) => {
headerNotificationSuite
.setCaptureElements('#header-notification-dropdown ~ .dropdown-menu')
// Test for the collapsed header in mobile
gemini.suite('Header-mobile', (headerMobileSuite) => {
headerMobileSuite
.setCaptureElements('nav.navbar .container')
// Ignore Cyland's logo as it's a gif
.ignoreElements('.client--cyland img.img-brand')
.skip(/Desktop/)
.before((actions) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG);
})
.capture('mobile header')
.capture('expanded mobile header', (actions, find) => {
actions.click(find('nav.navbar .navbar-toggle'));
// Wait for the header to expand
actions.wait(500);
actions.waitForElementToShow('#header-notification-dropdown', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
})
.capture('expanded user dropdown', (actions, find) => {
actions.click(find('#nav-route-user-dropdown'));
})
.capture('expanded notifications dropdown', (actions, find) => {
actions.click(find('#header-notification-dropdown'));
});
});
});
// Test for the collapsed header in mobile
gemini.suite('Header-mobile', (headerMobileSuite) => {
headerMobileSuite
.setCaptureElements('nav.navbar .container')
// Ignore Cyland's logo as it's a gif
.ignoreElements('.client--cyland img.img-brand')
.skip(/Desktop/)
.before((actions, find) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
})
.capture('mobile header')
.capture('expanded mobile header', (actions, find) => {
actions.click(find('nav.navbar .navbar-toggle'));
// Wait for the header to expand
actions.wait(500);
})
.capture('expanded user dropdown', (actions, find) => {
actions.click(find('#nav-route-user-dropdown'));
})
.capture('expanded notifications dropdown', (actions, find) => {
actions.click(find('#header-notification-dropdown'));
});
});
gemini.suite('Collection', (collectionSuite) => {
collectionSuite
.setCaptureElements('.ascribe-accordion-list')
.before((actions, find) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
// Wait for the images to load
// FIXME: unfortuntately gemini doesn't support ignoring multiple elements from a single selector
// so we're forced to wait and hope that the images will all finish loading after 5s.
// We could also change the thumbnails with JS, but setting up a test user is probably easier.
actions.wait(TIMEOUTS.NORMAL);
})
.capture('collection')
.capture('expanded edition in collection', (actions, find) => {
actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget'));
// Wait for editions to load
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.LONG);
})
gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => {
collectionPlaceholderSuite
.setCaptureElements('.ascribe-accordion-list-placeholder')
.capture('collection empty search', (actions, find) => {
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'no search result');
actions.waitForElementToShow('.ascribe-accordion-list-placeholder', TIMEOUTS.NORMAL);
});
});
gemini.suite('PieceListBulkModal', (pieceListBulkModalSuite) => {
pieceListBulkModalSuite
.setCaptureElements('.piece-list-bulk-modal')
.capture('items selected', (actions, find) => {
gemini.suite('Collection', (collectionSuite) => {
collectionSuite
.setCaptureElements('.ascribe-accordion-list')
.before((actions) => {
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
// Wait for the images to load
// FIXME: unfortuntately gemini doesn't support ignoring multiple elements from a single selector
// so we're forced to wait and hope that the images will all finish loading after 5s.
// We could also change the thumbnails with JS, but setting up a test user is probably easier.
actions.wait(TIMEOUTS.NORMAL);
})
.capture('collection')
.capture('expanded edition in collection', (actions, find) => {
actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget'));
// Wait for editions to load
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.NORMAL);
actions.click('.ascribe-table thead tr input[type="checkbox"]');
actions.waitForElementToShow('.piece-list-bulk-modal');
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.LONG);
});
});
});
gemini.suite('PieceListToolbar', (pieceListToolbarSuite) => {
pieceListToolbarSuite
.setCaptureElements('.ascribe-piece-list-toolbar')
.capture('piece list toolbar')
.capture('piece list toolbar search filled', (actions, find) => {
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'search text');
actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search', TIMEOUTS.NORMAL);
})
gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => {
pieceListToolbarOrderWidgetSuite
.setCaptureElements('#ascribe-piece-list-toolbar-order-widget-dropdown',
'#ascribe-piece-list-toolbar-order-widget-dropdown ~ .dropdown-menu')
.capture('expanded order dropdown', (actions, find) => {
actions.click(find('#ascribe-piece-list-toolbar-order-widget-dropdown'));
// Wait as the dropdown screenshot still includes the collection in the background
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
});
});
gemini.suite('Filter widget dropdown', (pieceListToolbarFilterWidgetSuite) => {
pieceListToolbarFilterWidgetSuite
.setCaptureElements('#ascribe-piece-list-toolbar-filter-widget-dropdown',
'#ascribe-piece-list-toolbar-filter-widget-dropdown ~ .dropdown-menu')
.capture('expanded filter dropdown', (actions, find) => {
actions.click(find('#ascribe-piece-list-toolbar-filter-widget-dropdown'));
// Wait as the dropdown screenshot still includes the collection in the background
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
});
});
});
gemini.suite('Register work', (registerSuite) => {
registerSuite
.setUrl('/register_piece')
.before((actions, find) => {
// The editions options are only rendered after the whitelabel is fetched, so
// we have to wait for it here
// We have to check for the sibling checkbox class as the input itself is hidden
actions.waitForElementToShow('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox', TIMEOUTS.NORMAL);
})
.capture('register work', (actions, find) => {
// The uploader options are only rendered after the user is fetched, so
// we also have to wait for it here
actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', TIMEOUTS.NORMAL);
})
.capture('register work filled', (actions, find) => {
actions.sendKeys(find('.ascribe-form input[name="artist_name"]'), 'artist name');
actions.sendKeys(find('.ascribe-form input[name="title"]'), 'title');
actions.sendKeys(find('.ascribe-form input[name="date_created"]'), 'date created');
})
.capture('register work filled with editions', (actions, find) => {
actions.click(find('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox'));
// Wait for transition
actions.wait(500);
actions.sendKeys(find('.ascribe-form input[name="num_editions"]'), '50');
gemini.suite('Collection placeholder', (collectionPlaceholderSuite) => {
collectionPlaceholderSuite
.setCaptureElements('.ascribe-accordion-list-placeholder')
.capture('collection empty search', (actions, find) => {
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'no search result');
actions.waitForElementToShow('.ascribe-accordion-list-placeholder', TIMEOUTS.NORMAL);
});
});
gemini.suite('Register work hash', (registerHashSuite) => {
registerHashSuite
.setUrl('/register_piece?method=hash')
.capture('register work hash method');
gemini.suite('PieceListBulkModal', (pieceListBulkModalSuite) => {
pieceListBulkModalSuite
.setCaptureElements('.piece-list-bulk-modal')
.capture('items selected', (actions, find) => {
actions.click(find('.ascribe-accordion-list-item .ascribe-accordion-list-item-edition-widget'));
// Wait for editions to load
actions.waitForElementToShow('.ascribe-accordion-list-item-table', TIMEOUTS.NORMAL);
actions.click('.ascribe-table thead tr input[type="checkbox"]');
actions.waitForElementToShow('.piece-list-bulk-modal');
});
});
});
gemini.suite('Register work upload', (registerUploadSuite) => {
registerUploadSuite
.setUrl('/register_piece?method=upload')
.capture('register work upload method');
});
});
gemini.suite('PieceListToolbar', (pieceListToolbarSuite) => {
pieceListToolbarSuite
.setCaptureElements('.ascribe-piece-list-toolbar')
.capture('piece list toolbar')
.capture('piece list toolbar search filled', (actions, find) => {
actions.sendKeys(find('.ascribe-piece-list-toolbar .search-bar input[type="text"]'), 'search text');
actions.waitForElementToShow('.ascribe-piece-list-toolbar .search-bar .icon-ascribe-search',
TIMEOUTS.NORMAL);
});
gemini.suite('User settings', (userSettingsSuite) => {
userSettingsSuite
.setUrl('/settings')
.before((actions, find) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
actions.waitForElementToShow('.settings-container', TIMEOUTS.NORMAL);
})
.capture('user settings');
gemini.suite('Order widget dropdown', (pieceListToolbarOrderWidgetSuite) => {
pieceListToolbarOrderWidgetSuite
.setCaptureElements('#ascribe-piece-list-toolbar-order-widget-dropdown',
'#ascribe-piece-list-toolbar-order-widget-dropdown ~ .dropdown-menu')
.capture('expanded order dropdown', (actions, find) => {
actions.click(find('#ascribe-piece-list-toolbar-order-widget-dropdown'));
// Wait as the dropdown screenshot still includes the collection in the background
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
});
});
gemini.suite('Filter widget dropdown', (pieceListToolbarFilterWidgetSuite) => {
pieceListToolbarFilterWidgetSuite
.setCaptureElements('#ascribe-piece-list-toolbar-filter-widget-dropdown',
'#ascribe-piece-list-toolbar-filter-widget-dropdown ~ .dropdown-menu')
.capture('expanded filter dropdown', (actions, find) => {
actions.click(find('#ascribe-piece-list-toolbar-filter-widget-dropdown'));
// Wait as the dropdown screenshot still includes the collection in the background
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
});
});
});
gemini.suite('Register work', (registerSuite) => {
registerSuite
.setUrl('/register_piece')
.before((actions) => {
// The editions options are only rendered after the whitelabel is fetched, so
// we have to wait for it here
// We have to check for the sibling checkbox class as the input itself is hidden
actions.waitForElementToShow('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox',
TIMEOUTS.NORMAL);
})
.capture('register work', (actions) => {
// The uploader options are only rendered after the user is fetched, so
// we also have to wait for it here
actions.waitForElementToShow('.file-drag-and-drop-dialog .present-options', TIMEOUTS.NORMAL);
})
.capture('register work filled', (actions, find) => {
actions.sendKeys(find('.ascribe-form input[name="artist_name"]'), 'artist name');
actions.sendKeys(find('.ascribe-form input[name="title"]'), 'title');
actions.sendKeys(find('.ascribe-form input[name="date_created"]'), 'date created');
})
.capture('register work filled with editions', (actions, find) => {
actions.click(find('.ascribe-form input[name="num_editions-checkbox"] ~ .checkbox'));
// Wait for transition
actions.wait(500);
actions.sendKeys(find('.ascribe-form input[name="num_editions"]'), '50');
});
gemini.suite('Register work hash', (registerHashSuite) => {
registerHashSuite
.setUrl('/register_piece?method=hash')
.capture('register work hash method');
});
gemini.suite('Register work upload', (registerUploadSuite) => {
registerUploadSuite
.setUrl('/register_piece?method=upload')
.capture('register work upload method');
});
});
gemini.suite('User settings', (userSettingsSuite) => {
userSettingsSuite
.setUrl('/settings')
.before((actions) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
actions.waitForElementToShow('.settings-container', TIMEOUTS.NORMAL);
})
.capture('user settings');
});
});
// Suite just to log out after suites have run
@ -216,7 +221,7 @@ gemini.suite('Authenticated', (suite) => {
logoutSuite
.setUrl('/logout')
.ignoreElements('.ascribe-body')
.capture('logout', (actions, find) => {
.capture('logout', (actions) => {
actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG);
});
});

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../environment');
const MAIN_USER = environment.MAIN_USER;
const TIMEOUTS = environment.TIMEOUTS;
@ -12,7 +11,7 @@ gemini.suite('Basic', (suite) => {
suite
.setUrl('/login')
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
@ -22,7 +21,7 @@ gemini.suite('Basic', (suite) => {
headerSuite
.setCaptureElements('nav.navbar .container')
.skip(/Mobile/)
.capture('desktop header', (actions, find) => {
.capture('desktop header', (actions) => {
actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL);
})
.capture('hover on active item', (actions, find) => {
@ -40,7 +39,7 @@ gemini.suite('Basic', (suite) => {
headerMobileSuite
.setCaptureElements('nav.navbar .container')
.skip(/Desktop/)
.capture('mobile header', (actions, find) => {
.capture('mobile header', (actions) => {
actions.waitForElementToShow('nav.navbar .container', TIMEOUTS.NORMAL);
})
.capture('expanded mobile header', (actions, find) => {
@ -56,7 +55,7 @@ gemini.suite('Basic', (suite) => {
gemini.suite('Footer', (footerSuite) => {
footerSuite
.setCaptureElements('.ascribe-footer')
.capture('footer', (actions, find) => {
.capture('footer', (actions) => {
actions.waitForElementToShow('.ascribe-footer', TIMEOUTS.NORMAL);
})
.capture('hover on footer item', (actions, find) => {
@ -64,14 +63,14 @@ gemini.suite('Basic', (suite) => {
actions.mouseMove(footerItem);
})
.capture('hover on footer social item', (actions, find) => {
const footerSocialItem = find('.ascribe-footer a.social')
const footerSocialItem = find('.ascribe-footer a.social');
actions.mouseMove(footerSocialItem);
});
});
gemini.suite('Login', (loginSuite) => {
loginSuite
.capture('login', (actions, find) => {
.capture('login', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
})
.capture('hover on login submit', (actions, find) => {
@ -97,7 +96,7 @@ gemini.suite('Basic', (suite) => {
gemini.suite('Sign up', (signUpSuite) => {
signUpSuite
.setUrl('/signup')
.capture('sign up', (actions, find) => {
.capture('sign up', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
})
.capture('sign up form filled with focus', (actions, find) => {
@ -113,7 +112,7 @@ gemini.suite('Basic', (suite) => {
gemini.suite('Password reset', (passwordResetSuite) => {
passwordResetSuite
.setUrl('/password_reset')
.capture('password reset', (actions, find) => {
.capture('password reset', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
})
.capture('password reset form filled with focus', (actions, find) => {
@ -127,12 +126,13 @@ gemini.suite('Basic', (suite) => {
gemini.suite('Coa verify', (coaVerifySuite) => {
coaVerifySuite
.setUrl('/coa_verify')
.capture('coa verify', (actions, find) => {
.capture('coa verify', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
})
.capture('coa verify form filled with focus', (actions, find) => {
actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text');
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature');
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'),
'sample signature');
})
.capture('coa verify form filled', (actions, find) => {
actions.click(find('.ascribe-login-header'));

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../environment');
const MAIN_USER = environment.MAIN_USER;
const TIMEOUTS = environment.TIMEOUTS;
@ -13,24 +12,24 @@ const editionUrl = `/editions/${environment.MAIN_EDITION_ID}`;
* Tests include accessing the piece / edition as the owner or as another user
* (we can just use an anonymous user in this case).
*/
gemini.suite('Work detail', (suite) => {
gemini.suite('Basic work detail', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
.before((actions) => {
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
// Wait for the social media buttons to appear
actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button', TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe',
TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button',
TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-media-player', TIMEOUTS.LONG);
});
gemini.suite('Basic piece', (basicPieceSuite) => {
basicPieceSuite
.setUrl(pieceUrl)
.capture('basic piece')
.capture('basic piece');
gemini.suite('Shmui', (shmuiSuite) => {
shmuiSuite.
@ -49,72 +48,97 @@ gemini.suite('Work detail', (suite) => {
.setUrl(editionUrl)
.capture('basic edition');
});
});
gemini.suite('Authenticated work detail', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions) => {
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
});
// Suite just to log us in before any other suites run
gemini.suite('Login', (loginSuite) => {
loginSuite
.setUrl('/login')
.ignoreElements('.ascribe-body')
.before((actions, find) => {
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
})
.capture('logged in', (actions, find) => {
console.log('logging in');
actions.sendKeys(find('.ascribe-login-wrapper input[name=email]'), MAIN_USER.email);
actions.sendKeys(find('.ascribe-login-wrapper input[name=password]'), MAIN_USER.password);
actions.click(find('.ascribe-login-wrapper button[type=submit]'));
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.NORMAL);
actions.waitForElementToShow('.ascribe-accordion-list:not(.ascribe-loading-position)', TIMEOUTS.LONG);
});
});
gemini.suite('Authorized piece', (authorizedPieceSuite) => {
authorizedPieceSuite
.setUrl(pieceUrl)
.capture('authorized piece');
});
gemini.suite('Authorized edition', (authorizedEditionSuite) => {
authorizedEditionSuite
.setUrl(editionUrl)
.capture('authorized edition')
});
gemini.suite('Detail action buttons', (detailActionButtonSuite) => {
detailActionButtonSuite
.setUrl(editionUrl)
.capture('hover on action button', (actions, find) => {
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-default'));
})
.capture('hover on delete button', (actions, find) => {
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-tertiary'));
})
.capture('hover on info button', (actions, find) => {
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
})
.capture('expand info text', (actions, find) => {
actions.click(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
gemini.suite('Authenticated', (authenticatedSuite) => {
authenticatedSuite
.before((actions) => {
// Wait for the social media buttons to appear
actions.waitForElementToShow('.ascribe-social-button-list .fb-share-button iframe',
TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-social-button-list .twitter-share-button',
TIMEOUTS.SUPER_DUPER_EXTRA_LONG);
actions.waitForElementToShow('.ascribe-media-player', TIMEOUTS.LONG);
});
});
gemini.suite('Action form modal', (actionFormModalSuite) => {
actionFormModalSuite
.setUrl(editionUrl)
.setCaptureElements('.modal-dialog')
.capture('open email form', (actions, find) => {
// Add class names to make the action buttons easier to select
actions.executeJS(function (window) {
var actionButtons = window.document.querySelectorAll('.ascribe-detail-property .ascribe-button-list button.btn-default');
for (var ii = 0; ii < actionButtons.length; ++ii) {
if (actionButtons[ii].textContent) {
actionButtons[ii].className += ' ascribe-action-button-' + actionButtons[ii].textContent.toLowerCase();
}
}
gemini.suite('Authorized piece', (authorizedPieceSuite) => {
console.log('authorized piece');
authorizedPieceSuite
.setUrl(pieceUrl)
.capture('authorized piece');
});
gemini.suite('Authorized edition', (authorizedEditionSuite) => {
authorizedEditionSuite
.setUrl(editionUrl)
.capture('authorized edition');
});
gemini.suite('Detail action buttons', (detailActionButtonSuite) => {
detailActionButtonSuite
.setUrl(editionUrl)
.capture('hover on action button', (actions, find) => {
console.log('hover on action button');
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-default'));
})
.capture('hover on delete button', (actions, find) => {
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.btn-tertiary'));
})
.capture('hover on info button', (actions, find) => {
actions.mouseMove(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
})
.capture('expand info text', (actions, find) => {
actions.click(find('.ascribe-detail-property .ascribe-button-list button.glyphicon-question-sign'));
});
actions.click(find('.ascribe-detail-property .ascribe-button-list button.ascribe-action-button-email'));
});
// Wait for transition
actions.wait(1000);
});
gemini.suite('Action form modal', (actionFormModalSuite) => {
actionFormModalSuite
.setUrl(editionUrl)
.setCaptureElements('.modal-dialog')
.capture('open email form', (actions, find) => {
// Add class names to make the action buttons easier to select
// eslint-disable-next-line prefer-arrow-callback
actions.executeJS(function addButtonTypeAsClass(window) {
/* eslint-disable no-var, prefer-template */
var actionButtonsSelector = '.ascribe-detail-property .ascribe-button-list button.btn-default';
var actionButtons = window.document.querySelectorAll(actionButtonsSelector);
var ii = 0;
for (; ii < actionButtons.length; ++ii) {
if (actionButtons[ii].textContent) {
actionButtons[ii].className += ' ascribe-action-button-' +
actionButtons[ii].textContent.toLowerCase();
}
}
/* eslint-enable no-var */
});
actions.click(find('.ascribe-detail-property .ascribe-button-list button.ascribe-action-button-email'));
// Wait for transition
actions.wait(1000);
});
});
});
// Suite just to log out after suites have run
@ -122,10 +146,11 @@ gemini.suite('Work detail', (suite) => {
logoutSuite
.setUrl('/logout')
.ignoreElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
console.log('before log out');
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
})
.capture('logout', (actions, find) => {
.capture('logout', (actions) => {
actions.waitForElementToShow('.ascribe-login-wrapper', TIMEOUTS.LONG);
});
});

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../../environment');
const TIMEOUTS = environment.TIMEOUTS;
@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS;
gemini.suite('23vivi', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
});
@ -18,7 +17,7 @@ gemini.suite('23vivi', (suite) => {
gemini.suite('Landing', (landingSuite) => {
landingSuite
.setUrl('/')
.capture('landing', (actions, find) => {
.capture('landing', (actions) => {
// Wait for the logo to appear
actions.waitForElementToShow('.vivi23-landing--header-logo', TIMEOUTS.LONG);
});

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../../environment');
const TIMEOUTS = environment.TIMEOUTS;
@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS;
gemini.suite('Cyland', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
});
@ -20,7 +19,7 @@ gemini.suite('Cyland', (suite) => {
.setUrl('/')
// Ignore Cyland's logo as it's a gif
.ignoreElements('.cyland-landing img')
.capture('landing', (actions, find) => {
.capture('landing', (actions) => {
actions.waitForElementToShow('.cyland-landing img', TIMEOUTS.LONG);
});
});

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../../environment');
const MAIN_USER = environment.MAIN_USER;
const TIMEOUTS = environment.TIMEOUTS;
@ -11,7 +10,7 @@ const TIMEOUTS = environment.TIMEOUTS;
gemini.suite('Ikonotv', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
});
@ -22,9 +21,11 @@ gemini.suite('Ikonotv', (suite) => {
// Gemini complains if we try to capture the entire app for Ikonotv's landing page for some reason
.setCaptureElements('.ikonotv-landing')
.setTolerance(5)
.capture('landing', (actions, find) => {
.capture('landing', (actions) => {
// Stop background animation
actions.executeJS(function (window) {
// eslint-disable-next-line prefer-arrow-callback
actions.executeJS(function removeBackgroundAnimation(window) {
/* eslint-disable no-var */
var landingBackground = window.document.querySelector('.client--ikonotv .route--landing');
landingBackground.style.animation = 'none';
landingBackground.style.webkitAnimation = 'none';
@ -37,10 +38,10 @@ gemini.suite('Ikonotv', (suite) => {
// Ikono needs its own set of tests for some pre-authorization pages to wait for
// its logo to appear
gemini.suite('Ikonotv basic', (suite) => {
suite
gemini.suite('Ikonotv basic', (basicSuite) => {
basicSuite
.setCaptureElements('.ascribe-app')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../../environment');
const TIMEOUTS = environment.TIMEOUTS;
@ -10,7 +9,7 @@ const TIMEOUTS = environment.TIMEOUTS;
gemini.suite('Lumenus', (suite) => {
suite
.setCaptureElements('.ascribe-body')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins
actions.waitForElementToShow('.ascribe-app', TIMEOUTS.NORMAL);
});
@ -18,7 +17,7 @@ gemini.suite('Lumenus', (suite) => {
gemini.suite('Landing', (landingSuite) => {
landingSuite
.setUrl('/')
.capture('landing', (actions, find) => {
.capture('landing', (actions) => {
// Wait for the logo to appear
actions.waitForElementToShow('.wp-landing-wrapper img', TIMEOUTS.LONG);
});

View File

@ -1,6 +1,5 @@
'use strict';
const gemini = require('gemini');
const environment = require('../../environment');
const MAIN_USER = environment.MAIN_USER;
const TIMEOUTS = environment.TIMEOUTS;
@ -11,7 +10,7 @@ const TIMEOUTS = environment.TIMEOUTS;
gemini.suite('Whitelabel basic', (suite) => {
suite
.setCaptureElements('.ascribe-wallet-app > .container')
.before((actions, find) => {
.before((actions) => {
// This will be called before every nested suite begins unless that suite
// also defines a `.before()`
// FIXME: use a more generic class for this, like just '.ascribe-app'
@ -26,7 +25,7 @@ gemini.suite('Whitelabel basic', (suite) => {
.setUrl('/login')
// See Ikono
.skip(/Ikono/)
.capture('login', (actions, find) => {
.capture('login', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
// For some reason, the screenshots seem to keep catching the whitelabel login form
// on a refresh and without fonts loaded (maybe because they're the first tests run
@ -59,7 +58,7 @@ gemini.suite('Whitelabel basic', (suite) => {
.setUrl('/signup')
// See Ikono
.skip(/Ikono/)
.capture('sign up', (actions, find) => {
.capture('sign up', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
// Wait in case the form reloads due to other assets loading
actions.wait(500);
@ -77,7 +76,7 @@ gemini.suite('Whitelabel basic', (suite) => {
gemini.suite('Password reset', (passwordResetSuite) => {
passwordResetSuite
.setUrl('/password_reset')
.capture('password reset', (actions, find) => {
.capture('password reset', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
// Wait in case the form reloads due to other assets loading
actions.wait(500);
@ -93,14 +92,15 @@ gemini.suite('Whitelabel basic', (suite) => {
gemini.suite('Coa verify', (coaVerifySuite) => {
coaVerifySuite
.setUrl('/coa_verify')
.capture('coa verify', (actions, find) => {
.capture('coa verify', (actions) => {
actions.waitForElementToShow('.ascribe-form', TIMEOUTS.NORMAL);
// Wait in case the form reloads due to other assets loading
actions.wait(500);
})
.capture('coa verify form filled with focus', (actions, find) => {
actions.sendKeys(find('.ascribe-form input[name="message"]'), 'sample text');
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'), 'sample signature');
actions.sendKeys(find('.ascribe-form .ascribe-property-wrapper:nth-of-type(2) textarea'),
'sample signature');
})
.capture('coa verify form filled', (actions, find) => {
actions.click(find('.ascribe-login-header'));

View File

@ -0,0 +1,9 @@
{
"env": {
"mocha": true
},
"rules": {
"max-len": [2, { "code": 125 }],
"prefer-arrow-callback": [0]
}
}

View File

@ -1,10 +1,10 @@
# TL;DR
Copy the contents of `.env-template` to `.env` and [fill up the missing keys with
Copy the contents of `.env_template` to `.env` and [fill up the missing keys with
information from your SauceLabs account](#how-to-set-up-your-env-config-file).
```bash
$ npm install
$ npm run tunnel
$ npm run sauce-tunnel
$ npm test && git commit
```
@ -99,7 +99,7 @@ On the JavaScript side, we use:
## How to set up your `.env` config file
In the root of this repository there is a file called `.env-template`. Create a
In the root of this repository there is a file called `.env_template`. Create a
copy and call it `.env`. This file will store some values we need to connect to
Saucelabs.

View File

@ -1,25 +1,25 @@
'use strict';
const config = require('./config');
const colors = require('colors');
const sauceConnectLauncher = require('sauce-connect-launcher');
const config = require('./config');
let globalSauceProcess;
if (!process.env.SAUCE_USERNAME) {
console.log(colors.red('SAUCE_USERNAME is missing. Please check the README.md file.'));
process.exit(1); //eslint-disable-line no-process-exit
process.exit(1);
}
if (!process.env.SAUCE_ACCESS_KEY) {
console.log(colors.red('SAUCE_ACCESS_KEY is missing. Please check the README.md file.'));
process.exit(1); //eslint-disable-line no-process-exit
process.exit(1);
}
if (process.env.SAUCE_AUTO_CONNECT) {
before(function(done) {
before(function (done) {
console.log(colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.'));
// Creating the tunnel takes a bit of time. For this case we can safely disable Mocha timeouts.
this.timeout(0);

View File

@ -12,12 +12,12 @@ chai.should();
function testSuite(browserName, version, platform) {
describe(`[${browserName} ${version} ${platform}] Login logs users in`, function() {
describe(`[${browserName} ${version} ${platform}] Login logs users in`, function () {
// Set timeout to zero so Mocha won't time out.
this.timeout(0);
let browser;
before(function() {
before(function () {
// No need to inject `username` or `access_key`, by default the constructor
// looks up the values in `process.env.SAUCE_USERNAME` and `process.env.SAUCE_ACCESS_KEY`
browser = wd.promiseChainRemote('ondemand.saucelabs.com', 80);
@ -35,14 +35,14 @@ function testSuite(browserName, version, platform) {
});
});
after(function() {
after(function () {
return browser.quit();
});
it('should contain "Log in" in the title', function() {
return browser.
waitForElementByCss('.ascribe-login-wrapper', asserters.isDisplayed, 2000)
title().should.become('Log in');
it('should contain "Log in" in the title', function () {
return browser
.waitForElementByCss('.ascribe-login-wrapper', asserters.isDisplayed, 2000)
.title().should.become('Log in');
});
});
}

View File

@ -1,15 +1,17 @@
'use strict';
const config = require('./config'); //eslint-disable-line no-unused-vars
const colors = require('colors');
const sauceConnectLauncher = require('sauce-connect-launcher');
const config = require('./config'); // eslint-disable-line no-unused-vars
function connect() {
console.log(colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.'));
console.log(
colors.yellow('Setting up tunnel from Saucelabs to your lovely computer, will take a while.')
);
// Creating the tunnel takes a bit of time. For this case we can safely disable Mocha timeouts.
sauceConnectLauncher(function (err) {
sauceConnectLauncher((err) => {
if (err) {
console.error(err.message);
return;

223
webpack.config.js Normal file
View File

@ -0,0 +1,223 @@
/* eslint-disable strict, no-console, object-shorthand, prefer-template */
/* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */
'use strict';
const path = require('path');
const removeTrailingSlash = require('remove-trailing-slash');
const webpack = require('webpack');
const autoPrefixer = require('autoprefixer');
const combineLoaders = require('webpack-combine-loaders');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
require('dotenv').load({ silent: true });
const EXTRACT = process.env.NODE_ENV === 'extract';
const PRODUCTION = process.env.NODE_ENV === 'production';
const PATHS = {
APP: path.resolve(__dirname, 'js/app.js'),
BUILD: path.resolve(__dirname, 'build'),
DIST: path.resolve(__dirname, 'dist'),
NODE_MODULES: path.resolve(__dirname, 'node_modules'),
};
/** EXTERNAL DEFINITIONS INJECTED INTO APP **/
const DEFINITIONS = {
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
APP_VERSION: JSON.stringify(process.env.ONION_APP_VERSION || 'dev'),
API_URL: JSON.stringify(
removeTrailingSlash(process.env.ONION_API_URL || 'https://staging.ascribe.io/api')
),
APP_BASE_PATH: JSON.stringify(
removeTrailingSlash(process.env.ONION_BASE_PATH || '')
),
SERVER_URL: JSON.stringify(
removeTrailingSlash(process.env.ONION_SERVER_URL || 'https://staging.ascribe.io')
),
RAVEN_DSN_URL: JSON.stringify(process.env.RAVEN_DSN_URL || ''),
S3_ACCESS_KEY: JSON.stringify(process.env.S3_ACCESS_KEY || ''),
},
};
/** PLUGINS **/
const PLUGINS = [
new webpack.DefinePlugin(DEFINITIONS),
new webpack.NoErrorsPlugin(),
// Handle any dependencies that don't play nicely with System.import resolution
new CopyWebpackPlugin([
{
from: path.resolve(PATHS.NODE_MODULES, 'audiojs/audiojs'),
to: 'third_party/audiojs'
},
]),
// Generate index.html for app with link and style tags addded
new HtmlWebpackPlugin({
filename: 'index.html',
minify: PRODUCTION ? {
collapseWhitespace: true,
minifyJS: true,
removeComments: true,
removeRedundantAttributes: true
} : false,
template: path.resolve(__dirname, 'index_template.html'),
}),
];
const PROD_PLUGINS = [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
output: {
comments: false
}
}),
new webpack.LoaderOptionsPlugin({
debug: false,
minimize: true
}),
];
if (PRODUCTION || EXTRACT) {
// Extract stylesheets out of bundle
PLUGINS.push(
new ExtractTextPlugin(PRODUCTION ? 'css/styles.min.css' : 'css/styles.css', {
allChunks: true
})
);
}
if (PRODUCTION) {
PLUGINS.push(...PROD_PLUGINS);
}
/** LOADERS **/
const JS_LOADER = combineLoaders([
{
loader: 'babel',
query: {
cacheDirectory: true,
},
},
]);
const CSS_LOADER = combineLoaders([
{
loader: 'css',
query: {
sourceMap: true,
},
},
{ loader: 'postcss' },
]);
const SASS_LOADER = `${CSS_LOADER}!` + combineLoaders([
{
loader: 'sass',
query: {
precision: '8', // See https://github.com/twbs/bootstrap-sass#sass-number-precision
outputStyle: 'expanded',
sourceMap: true,
},
}
]);
const URL_FONT_LOADER = combineLoaders([
{
loader: 'url',
query: {
limit: 10000,
name: 'fonts/[name].[ext]',
},
},
]);
const FILE_FONT_LOADER = combineLoaders([
{
loader: 'file',
query: {
name: 'fonts/[name].[ext]',
},
},
]);
const LOADERS = [
{
test: /\.jsx?$/,
exclude: [PATHS.NODE_MODULES],
loader: JS_LOADER,
},
{
test: /\.s[ac]ss$/,
exclude: [PATHS.NODE_MODULES],
loader: PRODUCTION || EXTRACT ? ExtractTextPlugin.extract('style', SASS_LOADER)
: `style!${SASS_LOADER}`,
},
// Dependencies
// Shmui
{
test: /\.css$/,
include: [path.resolve(PATHS.NODE_MODULES, 'shmui')],
loader: `style!${CSS_LOADER}`,
},
// Fonts
// woffs and svgs are typically smaller should we can try to load them as a url
{
test: /\.(woff2?|svg)$/,
loader: URL_FONT_LOADER,
},
{
test: /\.(ttf|eot)$/,
loader: FILE_FONT_LOADER,
},
];
/** EXPORTED WEBPACK CONFIG **/
const config = {
entry: [
PRODUCTION || EXTRACT ? 'bootstrap-loader/extractStyles' : 'bootstrap-loader',
PATHS.APP
],
output: {
filename: PRODUCTION ? 'js/bundle.min.js' : 'js/bundle.js',
path: PRODUCTION ? PATHS.DIST : PATHS.BUILD,
publicPath: '/static/',
},
debug: !PRODUCTION,
devtool: PRODUCTION ? '#source-map' : '#inline-source-map',
resolve: {
extensions: ['', '.js', '.jsx'],
modules: ['node_modules'], // Don't use absolute path here to allow recursive matching
},
plugins: PLUGINS,
module: {
loaders: LOADERS,
},
postcss: [autoPrefixer()],
};
module.exports = config;