diff --git a/back/.eslintignore b/back/.eslintignore
deleted file mode 100644
index 44f3970..0000000
--- a/back/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-/client/
\ No newline at end of file
diff --git a/back/common/models/item.js b/back/common/models/item.js
index 81bcddf..fa93de8 100644
--- a/back/common/models/item.js
+++ b/back/common/models/item.js
@@ -1,368 +1,368 @@
const warehouseIds = [1, 44];
module.exports = Self => {
- Self.remoteMethod('calcCatalog', {
- description: 'Get the available and prices list for an item',
- accessType: 'READ',
- accepts: [
- {
- arg: 'id',
- type: 'Number',
- description: 'The item id'
- }, {
- arg: 'dated',
- type: 'Date',
- description: 'The date'
- }, {
- arg: 'addressFk',
- type: 'Number',
- description: 'The address id'
- }, {
- arg: 'agencyModeFk',
- type: 'Number',
- description: 'The agency id'
- }
- ],
- returns: {
- type: ['Object'],
- description: 'The item available and prices list',
- root: true,
- },
- http: {
- path: `/:id/calcCatalog`,
- verb: 'GET'
- }
- });
+ Self.remoteMethod('calcCatalog', {
+ description: 'Get the available and prices list for an item',
+ accessType: 'READ',
+ accepts: [
+ {
+ arg: 'id',
+ type: 'Number',
+ description: 'The item id'
+ }, {
+ arg: 'dated',
+ type: 'Date',
+ description: 'The date'
+ }, {
+ arg: 'addressFk',
+ type: 'Number',
+ description: 'The address id'
+ }, {
+ arg: 'agencyModeFk',
+ type: 'Number',
+ description: 'The agency id'
+ }
+ ],
+ returns: {
+ type: ['Object'],
+ description: 'The item available and prices list',
+ root: true,
+ },
+ http: {
+ path: `/:id/calcCatalog`,
+ verb: 'GET'
+ }
+ });
- Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
- Self.dataSource.connector.query(
- `CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
- [id, dated, addressFk, agencyModeFk],
- (err, res) => {
- if (err) return cb(err)
- return cb(null, res[0])
- }
- );
+ Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
+ Self.dataSource.connector.query(
+ `CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
+ [id, dated, addressFk, agencyModeFk],
+ (err, res) => {
+ if (err) return cb(err)
+ return cb(null, res[0])
+ }
+ );
+ };
+
+ Self.remoteMethod('catalog', {
+ description: 'Get the catalog',
+ accessType: 'READ',
+ accepts: [
+ {
+ arg: 'dated',
+ type: 'Date',
+ description: 'The available date'
+ }, {
+ arg: 'typeFk',
+ type: 'Number',
+ description: 'The item type id'
+ }, {
+ arg: 'categoryFk',
+ type: 'Number',
+ description: 'The item category id'
+ }, {
+ arg: 'search',
+ type: 'String',
+ description: 'The search string'
+ }, {
+ arg: 'order',
+ type: 'String',
+ description: 'The order string'
+ }, {
+ arg: 'limit',
+ type: 'Number',
+ description: 'The maximum number of registers'
+ }, {
+ arg: 'tagFilter',
+ type: ['Object'],
+ description: 'The tag filter object'
+ }
+ ],
+ returns: {
+ type: ['Object'],
+ description: 'The item list',
+ root: true,
+ },
+ http: {
+ path: `/catalog`,
+ verb: 'GET'
+ }
+ });
+
+ Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
+ let $ = Self.app.models;
+ let itemIds;
+
+ let inboundWhere = {
+ warehouseFk: {inq: warehouseIds},
+ available: {gt: 0},
+ dated: {lte: dated},
+ and: [
+ {or: [
+ {expired: {gt: dated}},
+ {expired: null}
+ ]}
+ ]
};
- Self.remoteMethod('catalog', {
- description: 'Get the catalog',
- accessType: 'READ',
- accepts: [
- {
- arg: 'dated',
- type: 'Date',
- description: 'The available date'
- }, {
- arg: 'typeFk',
- type: 'Number',
- description: 'The item type id'
- }, {
- arg: 'categoryFk',
- type: 'Number',
- description: 'The item category id'
- }, {
- arg: 'search',
- type: 'String',
- description: 'The search string'
- }, {
- arg: 'order',
- type: 'String',
- description: 'The order string'
- }, {
- arg: 'limit',
- type: 'Number',
- description: 'The maximum number of registers'
- }, {
- arg: 'tagFilter',
- type: ['Object'],
- description: 'The tag filter object'
- }
- ],
- returns: {
- type: ['Object'],
- description: 'The item list',
- root: true,
- },
- http: {
- path: `/catalog`,
- verb: 'GET'
- }
- });
+ // Applies base filters
- Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
- let $ = Self.app.models;
- let itemIds;
+ if (/^[0-9]+$/.test(search)) {
+ itemIds = [parseInt(search)];
+ } else {
+ let inbounds = await $.Inbound.find({
+ fields: ['itemFk'],
+ where: inboundWhere
+ });
+ itemIds = toValues(inbounds, 'itemFk');
- let inboundWhere = {
- warehouseFk: {inq: warehouseIds},
- available: {gt: 0},
- dated: {lte: dated},
- and: [
- {or: [
- {expired: {gt: dated}},
- {expired: null}
- ]}
- ]
+ if (categoryFk || typeFk || search) {
+ let where = {
+ id: {inq: itemIds}
};
- // Applies base filters
-
- if (/^[0-9]+$/.test(search)) {
- itemIds = [parseInt(search)];
- } else {
- let inbounds = await $.Inbound.find({
- fields: ['itemFk'],
- where: inboundWhere
- });
- itemIds = toValues(inbounds, 'itemFk');
-
- if (categoryFk || typeFk || search) {
- let where = {
- id: {inq: itemIds}
- };
-
- if (typeFk) {
- where.typeFk = typeFk;
- } else if (categoryFk) {
- let types = await $.ItemType.find({
- fields: ['id'],
- where: {categoryFk}
- });
- where.typeFk = {inq: toValues(types, 'id')};
- }
-
- if (search)
- where.longName = {like: `%${search}%`};
-
- let filter = {
- fields: ['id'],
- where
- };
-
- let items = await Self.find(filter);
- itemIds = items.map(i => i.id);
- }
+ if (typeFk) {
+ where.typeFk = typeFk;
+ } else if (categoryFk) {
+ let types = await $.ItemType.find({
+ fields: ['id'],
+ where: {categoryFk}
+ });
+ where.typeFk = {inq: toValues(types, 'id')};
}
- // Applies tag filters
+ if (search)
+ where.longName = {like: `%${search}%`};
- let baseItemIds = itemIds;
- let tagItems = [];
- let tagFilterIds = [];
+ let filter = {
+ fields: ['id'],
+ where
+ };
- if (tagFilter && tagFilter.length) {
- for (let filter of tagFilter) {
- let cond;
- let values = filter.values;
+ let items = await Self.find(filter);
+ itemIds = items.map(i => i.id);
+ }
+ }
- if (values.length)
- cond = {value: {inq: values}};
- else if (values.min && values.max)
- cond = {intValue: {between: [values.min, values.max]}};
- else if (values.min)
- cond = {intValue: {gte: values.min}};
- else if (values.max)
- cond = {intValue: {lte: values.max}};
+ // Applies tag filters
- let where = {
- itemFk: {inq: itemIds},
- tagFk: filter.tagFk
- };
- Object.assign(where, cond);
+ let baseItemIds = itemIds;
+ let tagItems = [];
+ let tagFilterIds = [];
- let itemTags = await $.ItemTag.find({
- fields: ['itemFk'],
- where
- });
- tagItems.push(toSet(itemTags, 'itemFk'));
- tagFilterIds.push(filter.tagFk);
- }
+ if (tagFilter && tagFilter.length) {
+ for (let filter of tagFilter) {
+ let cond;
+ let values = filter.values;
- itemIds = intersect(tagItems);
- }
+ if (values.length)
+ cond = {value: {inq: values}};
+ else if (values.min && values.max)
+ cond = {intValue: {between: [values.min, values.max]}};
+ else if (values.min)
+ cond = {intValue: {gte: values.min}};
+ else if (values.max)
+ cond = {intValue: {lte: values.max}};
- // Obtains distinct tags and it's distinct values
-
- let tags = [];
+ let where = {
+ itemFk: {inq: itemIds},
+ tagFk: filter.tagFk
+ };
+ Object.assign(where, cond);
- if (typeFk || search) {
- let tagValues = await $.ItemTag.find({
- fields: ['tagFk', 'value', 'intValue', 'priority'],
- where: {
- itemFk: {inq: itemIds},
- tagFk: {nin: tagFilterIds}
- },
- order: 'tagFk, value'
- });
- let tagValueMap = toMultiMap(tagValues, 'tagFk');
+ let itemTags = await $.ItemTag.find({
+ fields: ['itemFk'],
+ where
+ });
+ tagItems.push(toSet(itemTags, 'itemFk'));
+ tagFilterIds.push(filter.tagFk);
+ }
- for (let i = 0; i < tagItems.length; i++) {
- let tagFk = tagFilter[i].tagFk;
- let itemIds;
+ itemIds = intersect(tagItems);
+ }
- if (tagItems.length > 1) {
- let siblings = tagItems.filter(v => v != tagItems[i]);
- itemIds = intersect(siblings);
- } else
- itemIds = baseItemIds;
+ // Obtains distinct tags and it's distinct values
+
+ let tags = [];
- let tagValues = await $.ItemTag.find({
- fields: ['value', 'intValue', 'priority'],
- where: {
- itemFk: {inq: itemIds},
- tagFk: tagFk
- },
- order: 'value'
- });
+ if (typeFk || search) {
+ let tagValues = await $.ItemTag.find({
+ fields: ['tagFk', 'value', 'intValue', 'priority'],
+ where: {
+ itemFk: {inq: itemIds},
+ tagFk: {nin: tagFilterIds}
+ },
+ order: 'tagFk, value'
+ });
+ let tagValueMap = toMultiMap(tagValues, 'tagFk');
- tagValueMap.set(tagFk, tagValues);
- }
+ for (let i = 0; i < tagItems.length; i++) {
+ let tagFk = tagFilter[i].tagFk;
+ let itemIds;
- let tagIds = [...tagValueMap.keys()];
- tags = await $.Tag.find({
- fields: ['id', 'name', 'isQuantitative', 'unit'],
- where: {
- id: {inq: tagIds}
- }
- });
+ if (tagItems.length > 1) {
+ let siblings = tagItems.filter(v => v != tagItems[i]);
+ itemIds = intersect(siblings);
+ } else
+ itemIds = baseItemIds;
- for (let tag of tags) {
- let tagValues = tagValueMap.get(tag.id);
-
- let filter = tagFilter && tagFilter.find(i => i.tagFk == tag.id);
- filter = filter && filter.values;
-
- let values = toSet(tagValues, 'value');
- if (Array.isArray(filter))
- values = new Set([...filter, ...values]);
-
- if (tag.isQuantitative) {
- let intValues = toValues(tagValues, 'intValue');
-
- if (filter) {
- if (filter.min) intValues.push(filter.min);
- if (filter.max) intValues.push(filter.max);
- }
-
- let min = Math.min(...intValues);
- let max = Math.max(...intValues);
- let dif = max - min;
- let digits = new String(dif).length;
- let step = Math.pow(10, digits - 1);
- if (digits > 1 && step * 5 > dif) step /= 10;
-
- Object.assign(tag, {
- step,
- min: Math.floor(min / step) * step,
- max: Math.ceil(max / step) * step
- });
- }
-
- Object.assign(tag, {
- values: [...values],
- filter
- });
- }
- }
-
- // Obtains items data
-
- let items = await Self.find({
- fields: ['id', 'longName', 'subName', 'image'],
- where: {id: {inq: itemIds}},
- limit: limit,
- order: order,
- include: [
- {
- relation: 'tags',
- scope: {
- fields: ['value', 'tagFk'],
- where: {priority: {gt: 4}},
- order: 'priority',
- include: {
- relation: 'tag',
- scope: {fields: ['name']}
- }
- }
- }, {
- relation: 'inbounds',
- scope: {
- fields: ['available', 'dated', 'tableId'],
- where: inboundWhere,
- order: 'dated DESC',
- include: {
- relation: 'buy',
- scope: {fields: ['id', 'price3']}
- },
- }
- }
- ]
+ let tagValues = await $.ItemTag.find({
+ fields: ['value', 'intValue', 'priority'],
+ where: {
+ itemFk: {inq: itemIds},
+ tagFk: tagFk
+ },
+ order: 'value'
});
- for (let item of items) {
- item.inbound = item.inbounds()[0];
- item.buy = item.inbound && item.inbound.buy();
- item.available = sum(item.inbounds(), 'available');
+ tagValueMap.set(tagFk, tagValues);
+ }
+
+ let tagIds = [...tagValueMap.keys()];
+ tags = await $.Tag.find({
+ fields: ['id', 'name', 'isQuantitative', 'unit'],
+ where: {
+ id: {inq: tagIds}
+ }
+ });
+
+ for (let tag of tags) {
+ let tagValues = tagValueMap.get(tag.id);
+
+ let filter = tagFilter && tagFilter.find(i => i.tagFk == tag.id);
+ filter = filter && filter.values;
+
+ let values = toSet(tagValues, 'value');
+ if (Array.isArray(filter))
+ values = new Set([...filter, ...values]);
+
+ if (tag.isQuantitative) {
+ let intValues = toValues(tagValues, 'intValue');
+
+ if (filter) {
+ if (filter.min) intValues.push(filter.min);
+ if (filter.max) intValues.push(filter.max);
+ }
+
+ let min = Math.min(...intValues);
+ let max = Math.max(...intValues);
+ let dif = max - min;
+ let digits = new String(dif).length;
+ let step = Math.pow(10, digits - 1);
+ if (digits > 1 && step * 5 > dif) step /= 10;
+
+ Object.assign(tag, {
+ step,
+ min: Math.floor(min / step) * step,
+ max: Math.ceil(max / step) * step
+ });
}
- return {items, tags};
- };
-
- // Array functions
-
- function sum(array, key) {
- if (!Array.isArray(array)) return 0;
- return array.reduce((a, c) => a + c[key], 0);
+ Object.assign(tag, {
+ values: [...values],
+ filter
+ });
+ }
}
- function toMap(objects, key) {
- let map = new Map();
- for (let object of objects)
- map.set(object[key], object);
- return map;
- }
+ // Obtains items data
- function toMultiMap(objects, key) {
- let map = new Map();
- for (let object of objects) {
- let value = map.get(object[key]);
- if (!value) map.set(object[key], value = []);
- value.push(object);
- }
- return map;
- }
-
- function toSet(objects, key) {
- let set = new Set();
- for (let object of objects)
- set.add(object[key]);
- return set;
- }
-
- function toValues(objects, key) {
- return [...toSet(objects, key)];
- }
-
- function intersect(sets) {
- if (!sets.length) return [];
- let array = [];
- let mySets = sets.slice(0);
- let firstSet = mySets.shift();
-
- for (let value of firstSet) {
- let isOnAll = true;
-
- for (let set of mySets)
- if (!set.has(value)) {
- isOnAll = false;
- break;
+ let items = await Self.find({
+ fields: ['id', 'longName', 'subName', 'image'],
+ where: {id: {inq: itemIds}},
+ limit,
+ order,
+ include: [
+ {
+ relation: 'tags',
+ scope: {
+ fields: ['value', 'tagFk'],
+ where: {priority: {gt: 4}},
+ order: 'priority',
+ include: {
+ relation: 'tag',
+ scope: {fields: ['name']}
}
-
- if (isOnAll)
- array.push(value);
+ }
+ }, {
+ relation: 'inbounds',
+ scope: {
+ fields: ['available', 'dated', 'tableId'],
+ where: inboundWhere,
+ order: 'dated DESC',
+ include: {
+ relation: 'buy',
+ scope: {fields: ['id', 'price3']}
+ },
+ }
}
+ ]
+ });
- return array;
+ for (let item of items) {
+ item.inbound = item.inbounds()[0];
+ item.buy = item.inbound && item.inbound.buy();
+ item.available = sum(item.inbounds(), 'available');
}
+
+ return {items, tags};
+ };
};
+
+// Array functions
+
+function sum(array, key) {
+ if (!Array.isArray(array)) return 0;
+ return array.reduce((a, c) => a + c[key], 0);
+}
+
+function toMap(objects, key) {
+ let map = new Map();
+ for (let object of objects)
+ map.set(object[key], object);
+ return map;
+}
+
+function toMultiMap(objects, key) {
+ let map = new Map();
+ for (let object of objects) {
+ let value = map.get(object[key]);
+ if (!value) map.set(object[key], value = []);
+ value.push(object);
+ }
+ return map;
+}
+
+function toSet(objects, key) {
+ let set = new Set();
+ for (let object of objects)
+ set.add(object[key]);
+ return set;
+}
+
+function toValues(objects, key) {
+ return [...toSet(objects, key)];
+}
+
+function intersect(sets) {
+ if (!sets.length) return [];
+ let array = [];
+ let mySets = sets.slice(0);
+ let firstSet = mySets.shift();
+
+ for (let value of firstSet) {
+ let isOnAll = true;
+
+ for (let set of mySets)
+ if (!set.has(value)) {
+ isOnAll = false;
+ break;
+ }
+
+ if (isOnAll)
+ array.push(value);
+ }
+
+ return array;
+}
diff --git a/back/common/models/user.js b/back/common/models/user.js
index a6e6a77..a0b9fcc 100644
--- a/back/common/models/user.js
+++ b/back/common/models/user.js
@@ -2,45 +2,64 @@ const app = require('../../server/server');
const loopback = require('loopback');
const path = require('path');
-const config = {
- proto: 'http',
- host: 'localhost',
- port: 3000,
- from: 'nocontestar@verdnatura.es'
- // app.dataSources.email.settings.transports[0].auth.user
-};
+function getUrl() {
+ return app.get('rootUrl') || app.get('url');
+}
+
+function getFrom() {
+ return app.dataSources.email.settings.transports[0].auth.from;
+}
+
+class ValidationError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = 'ValidationError';
+ this.statusCode = 422;
+ }
+}
module.exports = function (Self) {
- const hostBase = `${config.proto}://${config.host}`
- const urlBase = `${hostBase}:8080`;
- const apiBase = `${hostBase}:3000/api`;
-
Self.afterRemote('create', async function(ctx, instance) {
+ const url = new URL(getUrl());
const options = {
type: 'email',
to: instance.email,
- from: config.from,
+ from: getFrom(),
subject: 'Thanks for registering',
template: path.resolve(__dirname, '../../views/verify.ejs'),
- redirect: `${urlBase}/#/login?emailConfirmed`,
+ redirect: `${getUrl()}#/login/${instance.email}?emailConfirmed`,
+ host: url.hostname,
+ port: url.port,
+ protocol: url.protocol.split(':')[0],
user: Self
};
- const res = await instance.verify(options);
- console.log('> verification email sent:', res);
+ instance.verify(options)
+ .then(res => console.log('> Verification email sent:', res));
});
+ Self.validatePassword = function(password) {
+ if (!password) {
+ throw new ValidationError('passwordEmpty');
+ }
+
+ const pattern = new RegExp('(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.{6,})')
+ if (!pattern.test(password)) {
+ throw new ValidationError('passwordRequeriments');
+ }
+ };
+
Self.on('resetPasswordRequest', async function(info) {
const renderer = loopback.template(path.resolve(__dirname, '../../views/reset-password.ejs'));
const html = renderer({
- url: `${urlBase}/#/reset-password?access_token=${info.accessToken.id}`
+ url: `${getUrl()}#/reset-password?access_token=${info.accessToken.id}`
});
await app.models.Email.send({
to: info.email,
- from: config.from,
+ from: getFrom(),
subject: 'Password reset',
html
});
- console.log('> sending password reset email to:', info.email);
+ console.log('> Sending password reset email to:', info.email);
});
};
diff --git a/back/common/models/user.json b/back/common/models/user.json
index 7110845..a0831b9 100644
--- a/back/common/models/user.json
+++ b/back/common/models/user.json
@@ -1,10 +1,15 @@
{
"name": "user",
"base": "User",
+ "options": {
+ "mysql": {
+ "table": "salix.user"
+ }
+ },
"idInjection": true,
"properties": {},
"restrictResetPasswordTokenScope": true,
- "emailVerificationRequired": true,
+ "emailVerificationRequired": false,
"validations": [],
"relations": {},
"acls": [
diff --git a/back/common/models/vn-model.js b/back/common/models/vn-model.js
new file mode 100644
index 0000000..e2bf54f
--- /dev/null
+++ b/back/common/models/vn-model.js
@@ -0,0 +1,58 @@
+
+const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
+
+module.exports = function(Self) {
+ Self.ParameterizedSQL = ParameterizedSQL;
+
+ Object.assign(Self, {
+ setup() {
+ Self.super_.setup.call(this);
+ },
+
+ rewriteDbError(replaceErrFunc) {
+ function replaceErr(err, replaceErrFunc) {
+ if (Array.isArray(err)) {
+ let errs = [];
+ for (let e of err)
+ errs.push(replaceErrFunc(e));
+ return errs;
+ }
+ return replaceErrFunc(err);
+ }
+
+ function rewriteMethod(methodName) {
+ const realMethod = this[methodName];
+ return async(data, options, cb) => {
+ if (options instanceof Function) {
+ cb = options;
+ options = null;
+ }
+
+ try {
+ const result = await realMethod.call(this, data, options);
+
+ if (cb) cb(null, result);
+ else return result;
+ } catch (err) {
+ let myErr = replaceErr(err, replaceErrFunc);
+ if (cb) cb(myErr);
+ else
+ throw myErr;
+ }
+ };
+ }
+
+ this.once('attached', () => {
+ this.remove =
+ this.deleteAll =
+ this.destroyAll = rewriteMethod.call(this, 'remove');
+ this.upsert = rewriteMethod.call(this, 'upsert');
+ this.create = rewriteMethod.call(this, 'create');
+ });
+ },
+
+ rawSql(query, params, options, cb) {
+ return this.dataSource.connector.executeP(query, params, options, cb);
+ }
+ });
+};
diff --git a/back/common/models/vn-model.json b/back/common/models/vn-model.json
new file mode 100644
index 0000000..ab3718e
--- /dev/null
+++ b/back/common/models/vn-model.json
@@ -0,0 +1,5 @@
+{
+ "name": "VnModel",
+ "base": "PersistedModel",
+ "validateUpsert": true
+}
diff --git a/back/common/models/zone.js b/back/common/models/zone.js
new file mode 100644
index 0000000..4ff821b
--- /dev/null
+++ b/back/common/models/zone.js
@@ -0,0 +1,33 @@
+
+module.exports = function (Self) {
+ Self.remoteMethod('getEventsForAddress', {
+ description: 'Returns delivery days for a postcode',
+ accepts: [
+ {
+ arg: 'geoFk',
+ type: 'Number',
+ description: 'The geo id'
+ }, {
+ arg: 'agencyModeFk',
+ type: 'Number',
+ description: 'The agency mode id'
+ }
+ ],
+ returns: {
+ type: 'Object',
+ root: true
+ },
+ http: {
+ path: `/getEvents`,
+ verb: 'GET'
+ }
+ });
+
+ Self.getEvents = async(geoFk, agencyModeFk) => {
+ let [events, exclusions] = await Self.rawSql(
+ `CALL zone_getEvents(?, ?)`,
+ [geoFk, agencyModeFk]
+ );
+ return {events, exclusions};
+ };
+};
diff --git a/back/common/models/zone.json b/back/common/models/zone.json
new file mode 100644
index 0000000..83cebd5
--- /dev/null
+++ b/back/common/models/zone.json
@@ -0,0 +1,9 @@
+{
+ "name": "Zone",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "zone"
+ }
+ }
+}
diff --git a/back/locales/en.json b/back/locales/en.json
new file mode 100644
index 0000000..138730d
--- /dev/null
+++ b/back/locales/en.json
@@ -0,0 +1,11 @@
+{
+ "login failed": "Login failed",
+ "is blank": "Cannot be blank",
+ "is invalid": "Invalid value",
+ "can't be blank": "Field cannot be blank",
+ "invalidData": "Invalid data",
+ "invalidEmail": "Invalid email",
+ "passwordEmpty": "Password cannot be empty",
+ "passwordRequeriments": "Password doesn't meet requirements",
+ "notUniqueEmail": "User already exists"
+}
\ No newline at end of file
diff --git a/back/locales/es.json b/back/locales/es.json
new file mode 100644
index 0000000..cab89f3
--- /dev/null
+++ b/back/locales/es.json
@@ -0,0 +1,17 @@
+{
+ "login failed": "Usuario o contraseña incorrectos",
+ "is blank": "No puede estar vacío",
+ "is invalid": "Valor inválido",
+ "can't be blank": "El campo no puede estar vacío",
+ "invalidData": "Los datos son inválidos",
+ "invalidEmail": "Correo inválido",
+ "passwordEmpty": "La contraseña no puede estar vacía",
+ "passwordRequeriments": "La contraseña no cumple los requisitos",
+ "notUniqueEmail": "El usuario ya existe",
+ "could not find a model with id checkout": "could not find a model with id checkout",
+ "Unknown \"Order\" id \"checkout\".": "Unknown \"Order\" id \"checkout\".",
+ "There is no method to handle GET /Client/1437/addresses": "There is no method to handle GET /Client/1437/addresses",
+ "could not find a model with id configure": "could not find a model with id configure",
+ "Unknown \"Order\" id \"configure\".": "Unknown \"Order\" id \"configure\".",
+ "could not find a model with id undefined": "could not find a model with id undefined"
+}
\ No newline at end of file
diff --git a/back/package-lock.json b/back/package-lock.json
index afce5fa..8a5db78 100644
--- a/back/package-lock.json
+++ b/back/package-lock.json
@@ -1839,6 +1839,26 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
},
+ "i18n": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/i18n/-/i18n-0.9.1.tgz",
+ "integrity": "sha512-ERo9WloOP2inRsJzAlzn4JDm3jvX7FW1+KB/JGXTzUVzi9Bsf4LNLXUQTMgM/aze4LNW/kvmxQX6bzg5UzqMJw==",
+ "requires": {
+ "debug": "*",
+ "make-plural": "^6.2.1",
+ "math-interval-parser": "^2.0.1",
+ "messageformat": "^2.3.0",
+ "mustache": "^4.0.1",
+ "sprintf-js": "^1.1.2"
+ },
+ "dependencies": {
+ "sprintf-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
+ }
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -2715,6 +2735,11 @@
}
}
},
+ "make-plural": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.1.tgz",
+ "integrity": "sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg=="
+ },
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@@ -2723,6 +2748,11 @@
"p-defer": "^1.0.0"
}
},
+ "math-interval-parser": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz",
+ "integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA=="
+ },
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -2758,6 +2788,42 @@
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
+ "messageformat": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz",
+ "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==",
+ "requires": {
+ "make-plural": "^4.3.0",
+ "messageformat-formatters": "^2.0.1",
+ "messageformat-parser": "^4.1.2"
+ },
+ "dependencies": {
+ "make-plural": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz",
+ "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==",
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "optional": true
+ }
+ }
+ },
+ "messageformat-formatters": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz",
+ "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg=="
+ },
+ "messageformat-parser": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz",
+ "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg=="
+ },
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -2976,6 +3042,11 @@
"safe-buffer": "^5.1.2"
}
},
+ "mustache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
+ "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA=="
+ },
"mute-stream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
diff --git a/back/package.json b/back/package.json
index 1f38430..6346024 100644
--- a/back/package.json
+++ b/back/package.json
@@ -6,7 +6,6 @@
"node": ">=6"
},
"scripts": {
- "lint": "eslint .",
"start": "node .",
"posttest": "npm run lint && npm audit"
},
@@ -15,6 +14,7 @@
"cors": "^2.5.2",
"fs-extra": "^8.1.0",
"helmet": "^3.22.0",
+ "i18n": "^0.9.1",
"loopback": "^3.27.0",
"loopback-boot": "^2.28.0",
"loopback-component-explorer": "^6.5.1",
diff --git a/back/server/boot/connector.js b/back/server/boot/connector.js
new file mode 100644
index 0000000..bab7091
--- /dev/null
+++ b/back/server/boot/connector.js
@@ -0,0 +1,17 @@
+
+module.exports = function enableAuthentication(server) {
+ const connector = server.dataSources.vn.connector
+
+ connector.executeAsync = function(query, params, options = {}, cb) {
+ return new Promise((resolve, reject) => {
+ this.execute(query, params, options, (error, response) => {
+ if (cb)
+ cb(error, response);
+ if (error)
+ reject(error);
+ else
+ resolve(response);
+ });
+ });
+ }
+};
diff --git a/back/server/config.local.json b/back/server/config.local.json
new file mode 100644
index 0000000..dae3bb6
--- /dev/null
+++ b/back/server/config.local.json
@@ -0,0 +1,3 @@
+{
+ "rootUrl": "http://localhost:8080"
+}
\ No newline at end of file
diff --git a/back/server/middleware.json b/back/server/middleware.json
index 8bac49d..cc0e9f3 100644
--- a/back/server/middleware.json
+++ b/back/server/middleware.json
@@ -43,6 +43,11 @@
"loopback#urlNotFound": {}
},
"final:after": {
- "strong-error-handler": {}
+ "./middleware/error-handler": {},
+ "strong-error-handler": {
+ "params": {
+ "log": false
+ }
+ }
}
}
diff --git a/back/server/middleware/error-handler.js b/back/server/middleware/error-handler.js
new file mode 100644
index 0000000..110115d
--- /dev/null
+++ b/back/server/middleware/error-handler.js
@@ -0,0 +1,28 @@
+
+module.exports = function() {
+ return function(err, req, res, next) {
+ const statusCode = err.statusCode;
+
+ switch(statusCode) {
+ case 422: // Validation error
+ if (err.details) {
+ let messages = err.details.messages;
+ for (let message in messages) {
+ let texts = messages[message];
+ for (let i = 0; i < texts.length; i++) {
+ if (!texts[i]) continue;
+ texts[i] = req.__(texts[i]);
+ }
+ }
+ err.message = req.__('invalidData');
+ break;
+ }
+ default:
+ if (statusCode >= 400 && statusCode < 500) {
+ err.message = req.__(err.message);
+ }
+ }
+
+ next(err);
+ };
+};
diff --git a/back/server/model-config.json b/back/server/model-config.json
index a890512..c38acd8 100644
--- a/back/server/model-config.json
+++ b/back/server/model-config.json
@@ -1,182 +1,181 @@
{
- "_meta": {
- "sources": [
- "loopback/common/models",
- "loopback/server/models",
- "../common/models",
- "./models"
- ],
- "mixins": [
- "loopback/common/mixins",
- "loopback/server/mixins",
- "../common/mixins",
- "./mixins"
- ]
- },
- "AccessToken": {
- "dataSource": "vn",
- "options": {
- "mysql": {
- "table": "salix.AccessToken"
- }
- }
- },
- "ACL": {
- "dataSource": "vn",
- "options": {
- "mysql": {
- "table": "salix.ACL"
- }
- }
- },
- "Role": {
- "dataSource": "vn",
- "options": {
- "mysql": {
- "table": "salix.Role"
- }
- }
- },
- "RoleMapping": {
- "dataSource": "vn",
- "options": {
- "mysql": {
- "table": "salix.RoleMapping"
- }
- }
- },
- "User": {
- "dataSource": "vn",
- "public": false
- },
- "user": {
- "dataSource": "db",
- "public": true,
- "options": {
- "mysql": {
- "table": "salix.user"
+ "_meta": {
+ "sources": [
+ "loopback/common/models",
+ "loopback/server/models",
+ "../common/models",
+ "./models"
+ ],
+ "mixins": [
+ "loopback/common/mixins",
+ "loopback/server/mixins",
+ "../common/mixins",
+ "./mixins"
+ ]
+ },
+ "AccessToken": {
+ "dataSource": "vn",
+ "options": {
+ "mysql": {
+ "table": "salix.AccessToken"
+ }
},
- "emailVerificationRequired": true
+ "relations": {
+ "user": {
+ "type": "belongsTo",
+ "model": "user",
+ "foreignKey": "userId"
+ }
+ }
+ },
+ "ACL": {
+ "dataSource": "vn",
+ "options": {
+ "mysql": {
+ "table": "salix.ACL"
+ }
+ }
+ },
+ "Role": {
+ "dataSource": "vn",
+ "options": {
+ "mysql": {
+ "table": "salix.Role"
+ }
+ }
+ },
+ "RoleMapping": {
+ "dataSource": "vn",
+ "options": {
+ "mysql": {
+ "table": "salix.RoleMapping"
+ }
+ }
+ },
+ "user": {
+ "dataSource": "vn"
+ },
+ "Email": {
+ "dataSource": "email"
+ },
+ "Account": {
+ "dataSource": "vn"
+ },
+ "Address": {
+ "dataSource": "vn"
+ },
+ "AgencyMode": {
+ "dataSource": "vn"
+ },
+ "AlertLevel": {
+ "dataSource": "vn"
+ },
+ "Buy": {
+ "dataSource": "vn"
+ },
+ "Client": {
+ "dataSource": "vn"
+ },
+ "Container": {
+ "dataSource": "storage"
+ },
+ "Country": {
+ "dataSource": "vn"
+ },
+ "DeliveryMethod": {
+ "dataSource": "vn"
+ },
+ "Inbound": {
+ "dataSource": "vn"
+ },
+ "Image": {
+ "dataSource": "vn"
+ },
+ "ImageCollection": {
+ "dataSource": "vn"
+ },
+ "ImageCollectionSize": {
+ "dataSource": "vn"
+ },
+ "ItemCategory": {
+ "dataSource": "vn"
+ },
+ "Item": {
+ "dataSource": "vn"
+ },
+ "ItemTag": {
+ "dataSource": "vn"
+ },
+ "ItemType": {
+ "dataSource": "vn"
+ },
+ "Language": {
+ "dataSource": "vn"
+ },
+ "Link": {
+ "dataSource": "vn"
+ },
+ "MainAccountBank": {
+ "dataSource": "vn"
+ },
+ "New": {
+ "dataSource": "vn"
+ },
+ "NewTag": {
+ "dataSource": "vn"
+ },
+ "OrderRow": {
+ "dataSource": "vn"
+ },
+ "OrderTicket": {
+ "dataSource": "vn"
+ },
+ "Order": {
+ "dataSource": "vn"
+ },
+ "Province": {
+ "dataSource": "vn"
+ },
+ "Sale": {
+ "dataSource": "vn"
+ },
+ "State": {
+ "dataSource": "vn"
+ },
+ "Tag": {
+ "dataSource": "vn"
+ },
+ "Ticket": {
+ "dataSource": "vn"
+ },
+ "TicketState": {
+ "dataSource": "vn"
+ },
+ "TicketTracking": {
+ "dataSource": "vn"
+ },
+ "UserPassword": {
+ "dataSource": "vn"
+ },
+ "UserSession": {
+ "dataSource": "vn"
+ },
+ "Visit": {
+ "dataSource": "vn"
+ },
+ "VisitAccess": {
+ "dataSource": "vn"
+ },
+ "VisitAgent": {
+ "dataSource": "vn"
+ },
+ "VisitUser": {
+ "dataSource": "vn"
+ },
+ "Warehouse": {
+ "dataSource": "vn"
+ },
+ "Zone": {
+ "dataSource": "vn"
}
- },
- "Email": {
- "dataSource": "email"
- },
- "Account": {
- "dataSource": "vn"
- },
- "Address": {
- "dataSource": "vn"
- },
- "AgencyMode": {
- "dataSource": "vn"
- },
- "AlertLevel": {
- "dataSource": "vn"
- },
- "Buy": {
- "dataSource": "vn"
- },
- "Client": {
- "dataSource": "vn"
- },
- "Container": {
- "dataSource": "storage"
- },
- "Country": {
- "dataSource": "vn"
- },
- "DeliveryMethod": {
- "dataSource": "vn"
- },
- "Inbound": {
- "dataSource": "vn"
- },
- "Image": {
- "dataSource": "vn"
- },
- "ImageCollection": {
- "dataSource": "vn"
- },
- "ImageCollectionSize": {
- "dataSource": "vn"
- },
- "ItemCategory": {
- "dataSource": "vn"
- },
- "Item": {
- "dataSource": "vn"
- },
- "ItemTag": {
- "dataSource": "vn"
- },
- "ItemType": {
- "dataSource": "vn"
- },
- "Language": {
- "dataSource": "vn"
- },
- "Link": {
- "dataSource": "vn"
- },
- "MainAccountBank": {
- "dataSource": "vn"
- },
- "New": {
- "dataSource": "vn"
- },
- "NewTag": {
- "dataSource": "vn"
- },
- "OrderRow": {
- "dataSource": "vn"
- },
- "OrderTicket": {
- "dataSource": "vn"
- },
- "Order": {
- "dataSource": "vn"
- },
- "Province": {
- "dataSource": "vn"
- },
- "Sale": {
- "dataSource": "vn"
- },
- "State": {
- "dataSource": "vn"
- },
- "Tag": {
- "dataSource": "vn"
- },
- "Ticket": {
- "dataSource": "vn"
- },
- "TicketState": {
- "dataSource": "vn"
- },
- "TicketTracking": {
- "dataSource": "vn"
- },
- "UserPassword": {
- "dataSource": "vn"
- },
- "UserSession": {
- "dataSource": "vn"
- },
- "Visit": {
- "dataSource": "vn"
- },
- "VisitAccess": {
- "dataSource": "vn"
- },
- "VisitAgent": {
- "dataSource": "vn"
- },
- "VisitUser": {
- "dataSource": "vn"
- },
- "Warehouse": {
- "dataSource": "vn"
- }
}
diff --git a/back/server/server.js b/back/server/server.js
index 7bfa487..db8267f 100644
--- a/back/server/server.js
+++ b/back/server/server.js
@@ -5,19 +5,23 @@
'use strict';
-var loopback = require('loopback');
-var boot = require('loopback-boot');
+const loopback = require('loopback');
+const boot = require('loopback-boot');
+const i18n = require('i18n');
-var app = module.exports = loopback();
+const app = module.exports = loopback();
+
+i18n.configure({directory: `${__dirname}/../locales`});
+app.use(i18n.init);
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
- var baseUrl = app.get('url').replace(/\/$/, '');
+ const baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
- var explorerPath = app.get('loopback-component-explorer').mountPath;
+ const explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
diff --git a/quasar.conf.js b/quasar.conf.js
index 83e9cf7..6f0d0be 100644
--- a/quasar.conf.js
+++ b/quasar.conf.js
@@ -65,7 +65,11 @@ module.exports = function (ctx) {
'QSelect',
'QSeparator',
'QSlideTransition',
+ 'QSpace',
'QSpinner',
+ 'QStepper',
+ 'QStep',
+ 'QStepperNavigation',
'QTab',
'QTabs',
'QTabPanel',
diff --git a/src/boot/axios.js b/src/boot/axios.js
index bb9e4fa..bc0f8a5 100644
--- a/src/boot/axios.js
+++ b/src/boot/axios.js
@@ -7,9 +7,14 @@ export default async ({ app, Vue }) => {
axios.interceptors.request.use(function (config) {
const $state = Vue.prototype.$state
+
if ($state.user.loggedIn) {
config.headers.Authorization = $state.user.token
}
+ if (config.filter) {
+ if (!config.params) config.params = {}
+ config.params.filter = config.filter
+ }
return config
})
}
diff --git a/src/components/Page.js b/src/components/Page.js
index 6cde374..5dd5e91 100644
--- a/src/components/Page.js
+++ b/src/components/Page.js
@@ -1,10 +1,12 @@
import Toolbar from './Toolbar'
+import Portal from './Portal'
import LbScroll from './LbScroll'
import FullImage from './FullImage'
export default {
components: {
Toolbar,
+ Portal,
LbScroll,
FullImage
},
diff --git a/src/components/Portal.vue b/src/components/Portal.vue
new file mode 100644
index 0000000..358ec3b
--- /dev/null
+++ b/src/components/Portal.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/src/i18n/en-us/index.js b/src/i18n/en-us/index.js
index 8cea978..96da0c1 100644
--- a/src/i18n/en-us/index.js
+++ b/src/i18n/en-us/index.js
@@ -75,6 +75,10 @@ export default {
]
},
+ // errors
+ internalServerError: 'Internal server error',
+ somethingWentWrong: 'Something went wrong',
+
// Layout
login: 'Login',
logout: 'Logout',
@@ -88,12 +92,13 @@ export default {
notRememberPassword: 'I don\'t remember my password',
inputEmail: 'Input email',
inputPassword: 'Input password',
+ emailConfirmedSuccessfully: 'E-mail confirmed succesfully',
// register
register: 'Register',
fillData: 'Fill the data',
notYetUser: 'You are not yet a user?',
- userRegistered: 'User registered successfully',
+ userRegistered: 'User registered successfully, we\'ve sent you an e-mail to verify your account',
repeatPasswordError: 'Passwords doesn\'t match',
// recover
diff --git a/src/i18n/es-es/index.js b/src/i18n/es-es/index.js
index 786e4e6..160fd94 100644
--- a/src/i18n/es-es/index.js
+++ b/src/i18n/es-es/index.js
@@ -1,5 +1,5 @@
export default {
- // global
+ // Global
search: 'Buscar',
accept: 'Aceptar',
cancel: 'Cancelar',
@@ -75,12 +75,15 @@ export default {
]
},
- // layout
+ // Errors
+ internalServerError: 'Error interno del servidor',
+ somethingWentWrong: 'Algo salió mal',
+
+ // MainLayout
login: 'Iniciar sesión',
logout: 'Cerrar sesión',
- visitor: 'Visitante',
- // login
+ // Login
enter: 'Entrar',
email: 'Correo electrónico',
password: 'Contraseña',
@@ -88,25 +91,26 @@ export default {
notRememberPassword: 'No recuerdo mi contraseña',
inputEmail: 'Introduce el correo electrónico',
inputPassword: 'Introduce la contraseña',
+ emailConfirmedSuccessfully: 'Correo verificado correctamente',
- // register
- register: 'Registrarse',
+ // Register
+ register: 'Registrarme',
fillData: 'Rellena los datos',
notYetUser: '¿Todavía no eres usuario?',
- userRegistered: 'Usuario registrado correctamente',
+ userRegistered: 'Usuario registrado correctamente, te hemos enviado un correo para verificar tu dirección',
repeatPasswordError: 'Las contraseñas no coinciden',
- // recover
+ // Recover
rememberPassword: 'Recordar contraseña',
dontWorry: '¡No te preocupes!',
weSendEmail: 'Te enviaremos un correo para restablecer tu contraseña',
weHaveSentEmailToRecover: 'Te hemos enviado un correo donde podrás recuperar tu contraseña',
- // reset
+ // Reset
resetPassword: 'Restaurar contraseña',
passwordResetSuccessfully: 'Contraseña modificada correctamente',
- // menu
+ // Menu
home: 'Inicio',
catalog: 'Catálogo',
orders: 'Pedidos',
@@ -128,7 +132,7 @@ export default {
addresses: 'Direcciones',
addressEdit: 'Editar dirección',
- // home
+ // Home
recentNews: 'Noticias recientes',
startOrder: 'Empezar pedido',
@@ -147,7 +151,7 @@ export default {
n1InPrice: 'Nº1 en precio',
ourBigVolumeAllows: 'Nuestro gran volumen nos permite ofrecerte los mejores precios',
- // catalog
+ // Catalog
more: 'Más',
noItemsFound: 'No se han encontrado artículos',
pleaseSetFilter: 'Por favor, establece un filtro usando el menú de la derecha',
@@ -170,19 +174,19 @@ export default {
siceAsc: 'Medida ascendente',
sizeDesc: 'Medida descendente',
- // orders
+ // Orders
pending: 'Pendientes',
confirmed: 'Confirmados',
- // orders/pending
+ // Pending
pendingConfirmtion: 'Pendientes de confirmar',
noOrdersFound: 'No se han encontrado pedidos',
- // orders/confirmed
+ // Confirmed
ordersMadeAt: 'Pedidos realizados en',
packages: '{n} bultos',
- // order
+ // Order
total: 'Total',
confirm: 'Confirmar',
delivery: 'Fecha de entrega',
@@ -191,7 +195,24 @@ export default {
warehouse: 'Almacén',
configure: 'Configurar',
- // order/checkout
+ // OrderRows
+
+ rows: 'Lineas',
+
+ // OrderConfigure
+
+ deliveryMethod: 'Método de entrega',
+ pickupDate: 'Fecha de recogida',
+ deliveryAgency: 'Agencia de transporte',
+ pickupStore: 'Almacén de recogida',
+ deliveryAddress: 'Dirección de entrega',
+ optionalAddress: 'Dirección (Opcional)',
+ optionalAddressInfo: 'Para pedidos de recogida la dirección se utiliza como referencia cuando se dispone de varias direcciones y evitar que pedidos de tiendas diferentes se fusionen',
+ addressHint: 'Las fechas de entrega varian en función de la dirección',
+ storePickup: 'Recogida en almacén',
+ homeDelivery: 'Entrega a domicilio',
+
+ // OrderCheckout
checkout: 'Finalizar pedido',
orderSummary: 'Resumen del pedido',
accountsSummary: 'Resumen de cuentas',
@@ -217,13 +238,13 @@ export default {
youExceededCredit: 'Has excedido tu crédito, por favor realiza el pago para que podamos preparar tu pedido.',
notes: 'Notas',
- // conditions
+ // Conditions
conditionsDesc: 'Te aseguramos que el pedido llegara a tu casa/tienda en menos de 24/48 horas (Dependiendo de en que zona te encuentres).',
- // about
+ // About
aboutDesc: 'Verdnatura te ofrece todos los servicios que necesita tu floristería.',
- // connections
+ // Connections
nConnections: '{0} connexiones',
refreshRate: 'Frecuencia de actualización',
lastAction: 'Última acción',
@@ -231,27 +252,27 @@ export default {
nSeconds: '{0} segundos',
dontRefresh: 'No refrescar',
- // accessLog
+ // AccessLog
accessLog: 'Registro de accesos',
- // visits
+ // Visits
visitsCount: '{0} visitas, {1} nuevas',
- // new
+ // New
title: 'Título',
image: 'Imagen',
tag: 'Etiqueta',
priority: 'Prioridad',
text: 'Texto',
- // images
+ // Images
collection: 'Colección',
updateMatchingId: 'Actualizar ítems con id coincidente',
uploadAutomatically: 'Subir automáticamente',
imagesUploadSuccess: 'Imágenes subidas correctamente',
imagesUploadFailed: 'Algunas imágenes no se ha podido subir',
- // user
+ // User
userName: 'Nombre de usuario',
nickname: 'Nombre a mostrar',
language: 'Idioma',
@@ -270,14 +291,14 @@ export default {
passwordsDontMatch: 'Las contraseñas no coinciden',
passwordChanged: '¡Contraseña modificada correctamente!',
- // addresses
+ // Addresses
setAsDefault: 'Establecer como predeterminada',
addressSetAsDefault: 'Dirección establecida como predeterminada',
addressRemoved: 'Dirección eliminada',
areYouSureDeleteAddress: '¿Seguro que quieres eliminar la dirección?',
addressCreated: '¡Dirección creada correctamente!',
- // address
+ // Address
consignatary: 'Consignatario',
street: 'Dirección',
city: 'City',
diff --git a/src/layouts/LoginLayout.vue b/src/layouts/LoginLayout.vue
index eab4996..bd71b2e 100644
--- a/src/layouts/LoginLayout.vue
+++ b/src/layouts/LoginLayout.vue
@@ -1,5 +1,5 @@
-
+
@@ -9,6 +9,14 @@
+
+
diff --git a/src/pages/OrderView.vue b/src/pages/Webshop/Pending/Rows.vue
similarity index 99%
rename from src/pages/OrderView.vue
rename to src/pages/Webshop/Pending/Rows.vue
index f71639c..11c62ac 100644
--- a/src/pages/OrderView.vue
+++ b/src/pages/Webshop/Pending/Rows.vue
@@ -77,7 +77,7 @@
import Page from 'components/Page'
export default {
- name: 'OrderView',
+ name: 'OrdersPendingRows',
mixins: [Page],
data () {
return {
diff --git a/src/pages/Order.vue b/src/pages/Webshop/Pending/View.vue
similarity index 93%
rename from src/pages/Order.vue
rename to src/pages/Webshop/Pending/View.vue
index 9f069d6..0c01b0a 100644
--- a/src/pages/Order.vue
+++ b/src/pages/Webshop/Pending/View.vue
@@ -26,12 +26,9 @@
:label="$t('agency')"
:value="order.agencyMode.name"
readonly/>
-
import('pages/Login.vue')
+ path: '/login/:email?',
+ component: () => import('pages/Login/Login.vue')
}, {
name: 'register',
path: '/register',
- component: () => import('pages/Register.vue')
+ component: () => import('pages/Login/Register.vue')
}, {
name: 'rememberPassword',
path: '/remember-password',
- component: () => import('pages/RememberPassword.vue')
+ component: () => import('pages/Login/RememberPassword.vue')
}, {
name: 'resetPassword',
path: '/reset-password',
- component: () => import('pages/ResetPassword.vue')
+ component: () => import('pages/Login/ResetPassword.vue')
}
]
}, {
@@ -29,115 +29,119 @@ const routes = [
{
name: '',
path: '',
- component: () => import('pages/Index.vue')
+ component: () => import('pages/Cms/Home.vue')
}, {
name: 'home',
path: '/home',
- component: () => import('pages/Index.vue')
+ component: () => import('pages/Cms/Home.vue')
}, {
name: 'catalog',
path: '/catalog/:category?/:type?',
- component: () => import('pages/Catalog.vue')
+ component: () => import('pages/Webshop/Catalog.vue')
}, {
name: 'orders',
path: '/orders',
- component: () => import('pages/Orders.vue'),
+ component: () => import('pages/Webshop/Orders.vue'),
children: [
{
name: 'pending',
path: 'pending',
- component: () => import('pages/Pending.vue')
+ component: () => import('pages/Webshop/Pending.vue')
}, {
name: 'confirmed',
path: 'confirmed',
- component: () => import('pages/Confirmed.vue')
+ component: () => import('pages/Webshop/Confirmed.vue')
}
]
}, {
name: 'order',
path: '/order/:id',
- component: () => import('pages/Order.vue'),
+ component: () => import('pages/Webshop/Pending/View.vue'),
children: [
{
- name: 'view',
- path: 'view',
- component: () => import('pages/OrderView.vue')
+ name: '',
+ path: '',
+ component: () => import('pages/Webshop/Pending/Rows.vue')
+ }, {
+ name: 'configure',
+ path: 'configure',
+ component: () => import('pages/Webshop/Pending/Configure.vue')
}, {
name: 'checkout',
path: 'checkout',
- component: () => import('pages/OrderCheckout.vue')
+ component: () => import('pages/Webshop/Pending/Checkout.vue')
}
]
}, {
name: 'ticket',
path: '/ticket/:id',
- component: () => import('pages/Ticket.vue')
+ component: () => import('pages/Webshop/Confirmed/View.vue')
}, {
name: 'conditions',
path: '/conditions',
- component: () => import('pages/Conditions.vue')
+ component: () => import('pages/Cms/Conditions.vue')
}, {
name: 'about',
path: '/about',
- component: () => import('pages/About.vue')
+ component: () => import('pages/Cms/About.vue')
}, {
name: 'admin',
path: '/admin',
- component: () => import('pages/Admin.vue'),
+ component: () => import('pages/Admin/Index.vue'),
children: [
{
name: 'panel',
path: 'panel',
- component: () => import('pages/Panel.vue')
+ component: () => import('pages/Admin/Panel.vue')
}, {
name: 'users',
path: 'users',
- component: () => import('pages/Users.vue')
+ component: () => import('pages/Admin/Users.vue')
}, {
name: 'connections',
path: 'connections',
- component: () => import('pages/Connections.vue')
+ component: () => import('pages/Admin/Connections.vue')
}, {
name: 'visits',
path: 'visits',
- component: () => import('pages/Visits.vue')
+ component: () => import('pages/Admin/Visits.vue')
}, {
name: 'news',
path: 'news',
- component: () => import('pages/News.vue')
+ component: () => import('pages/Admin/News.vue')
}, {
name: 'images',
path: 'images',
- component: () => import('pages/Images.vue')
+ component: () => import('pages/Admin/Images.vue')
}, {
name: 'items',
path: 'items',
- component: () => import('pages/Items.vue')
+ component: () => import('pages/Admin/Items.vue')
}
]
}, {
name: 'accessLog',
path: '/access-log/:user',
- component: () => import('pages/AccessLog.vue')
+ component: () => import('pages/Admin/AccessLog.vue')
}, {
name: 'newEdit',
path: '/new/:id?',
- component: () => import('pages/New.vue')
+ component: () => import('pages/Admin/New.vue')
}, {
name: 'user',
path: '/user',
- component: () => import('pages/User.vue'),
+ component: () => import('pages/Account/User.vue'),
props: route => ({
changePassword: String(route.query.changePassword) === 'true'
})
}, {
name: 'addresses',
path: '/addresses',
- component: () => import('pages/Addresses.vue')
+ component: () => import('pages/Account/Addresses.vue')
}, {
name: 'addressEdit',
path: '/address/:id?',
- component: () => import('pages/Address.vue')
+ component: () => import('pages/Account/Address.vue')
}
]
}