- the great reformat

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-02-06 18:23:46 -05:00
parent 3b54fd27bb
commit 4eb8faa5d9
383 changed files with 54009 additions and 52734 deletions

View File

@@ -1,69 +1,69 @@
import ApplicationActionTypes from "./application.types";
export const startLoading = () => ({
type: ApplicationActionTypes.START_LOADING,
type: ApplicationActionTypes.START_LOADING,
});
export const endLoading = (options) => ({
type: ApplicationActionTypes.END_LOADING,
payload: options,
type: ApplicationActionTypes.END_LOADING,
payload: options,
});
export const setBreadcrumbs = (breadcrumbs) => ({
type: ApplicationActionTypes.SET_BREAD_CRUMBS,
payload: breadcrumbs,
type: ApplicationActionTypes.SET_BREAD_CRUMBS,
payload: breadcrumbs,
});
export const calculateScheduleLoad = (rangeEnd) => ({
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD,
payload: rangeEnd,
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD,
payload: rangeEnd,
});
export const scheduleLoadSuccess = (load) => ({
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS,
payload: load,
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS,
payload: load,
});
export const scheduleLoadFailure = (error) => ({
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE,
payload: error,
type: ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE,
payload: error,
});
export const addRecentItem = (item) => ({
type: ApplicationActionTypes.ADD_RECENT_ITEM,
payload: item,
type: ApplicationActionTypes.ADD_RECENT_ITEM,
payload: item,
});
export const setSelectedHeader = (key) => ({
type: ApplicationActionTypes.SET_SELECTED_HEADER,
payload: key,
type: ApplicationActionTypes.SET_SELECTED_HEADER,
payload: key,
});
export const setJobReadOnly = (bool) => ({
type: ApplicationActionTypes.SET_JOB_READONLY,
payload: bool,
type: ApplicationActionTypes.SET_JOB_READONLY,
payload: bool,
});
export const setPartnerVersion = (version) => ({
type: ApplicationActionTypes.SET_PARTNER_VERSION,
payload: version,
type: ApplicationActionTypes.SET_PARTNER_VERSION,
payload: version,
});
export const setOnline = (isOnline) => ({
type: ApplicationActionTypes.SET_ONLINE_STATUS,
payload: isOnline,
type: ApplicationActionTypes.SET_ONLINE_STATUS,
payload: isOnline,
});
export const insertAuditTrail = ({ jobid, billid, operation }) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: { jobid, billid, operation },
export const insertAuditTrail = ({jobid, billid, operation}) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: {jobid, billid, operation},
});
export const setProblemJobs = (problemJobs) => ({
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
payload: problemJobs,
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
payload: problemJobs,
});
export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable,
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable,
});

View File

@@ -1,110 +1,110 @@
import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false,
online: true,
updateAvailable: false,
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
problemJobs: [],
scheduleLoad: {
load: {},
calculating: false,
error: null,
},
jobReadOnly: false,
partnerVersion: null,
loading: false,
online: true,
updateAvailable: false,
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
problemJobs: [],
scheduleLoad: {
load: {},
calculating: false,
error: null,
},
jobReadOnly: false,
partnerVersion: null,
};
const applicationReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
return {
...state,
updateAvailable: action.payload,
};
case ApplicationActionTypes.SET_SELECTED_HEADER:
return {
...state,
selectedHeader: action.payload,
};
case ApplicationActionTypes.SET_ONLINE_STATUS:
return {
...state,
online: action.payload,
};
case ApplicationActionTypes.ADD_RECENT_ITEM:
return {
...state,
recentItems: updateRecentItemsArray(state, action.payload),
};
case ApplicationActionTypes.SET_BREAD_CRUMBS:
return {
...state,
breadcrumbs: action.payload,
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
return {
...state,
problemJobs: [],
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
return {
...state,
scheduleLoad: {
...state.scheduleLoad,
load: action.payload,
calculating: false,
},
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE:
return {
...state,
scheduleLoad: {
...state.scheduleLoad,
calculating: false,
error: action.payload,
},
};
case ApplicationActionTypes.START_LOADING:
return {
...state,
loading: true,
};
case ApplicationActionTypes.END_LOADING:
return {
...state,
loading: false,
};
switch (action.type) {
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
return {
...state,
updateAvailable: action.payload,
};
case ApplicationActionTypes.SET_SELECTED_HEADER:
return {
...state,
selectedHeader: action.payload,
};
case ApplicationActionTypes.SET_ONLINE_STATUS:
return {
...state,
online: action.payload,
};
case ApplicationActionTypes.ADD_RECENT_ITEM:
return {
...state,
recentItems: updateRecentItemsArray(state, action.payload),
};
case ApplicationActionTypes.SET_BREAD_CRUMBS:
return {
...state,
breadcrumbs: action.payload,
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
return {
...state,
problemJobs: [],
scheduleLoad: {...state.scheduleLoad, calculating: true, error: null},
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
return {
...state,
scheduleLoad: {
...state.scheduleLoad,
load: action.payload,
calculating: false,
},
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_FAILURE:
return {
...state,
scheduleLoad: {
...state.scheduleLoad,
calculating: false,
error: action.payload,
},
};
case ApplicationActionTypes.START_LOADING:
return {
...state,
loading: true,
};
case ApplicationActionTypes.END_LOADING:
return {
...state,
loading: false,
};
case ApplicationActionTypes.SET_JOB_READONLY:
return { ...state, jobReadOnly: action.payload };
case ApplicationActionTypes.SET_JOB_READONLY:
return {...state, jobReadOnly: action.payload};
case ApplicationActionTypes.SET_PARTNER_VERSION:
return { ...state, partnerVersion: action.payload };
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return { ...state, problemJobs: action.payload };
case ApplicationActionTypes.SET_PARTNER_VERSION:
return {...state, partnerVersion: action.payload};
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return {...state, problemJobs: action.payload};
}
default:
return state;
}
default:
return state;
}
};
export default applicationReducer;
const updateRecentItemsArray = (state, newItem) => {
//Check to see if the new item is in the list.
const matchingIndex = state.recentItems.findIndex((i) => i.id === newItem.id);
//Check to see if the new item is in the list.
const matchingIndex = state.recentItems.findIndex((i) => i.id === newItem.id);
if (matchingIndex >= 0) {
return [
newItem,
...state.recentItems.slice(0, matchingIndex),
...state.recentItems.slice(matchingIndex + 1, 9),
];
} else {
return [newItem, ...state.recentItems.slice(0, 9)];
}
if (matchingIndex >= 0) {
return [
newItem,
...state.recentItems.slice(0, matchingIndex),
...state.recentItems.slice(matchingIndex + 1, 9),
];
} else {
return [newItem, ...state.recentItems.slice(0, 9)];
}
};

View File

@@ -1,306 +1,303 @@
import dayjs from "../../utils/day";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import {all, call, put, select, takeLatest} from "redux-saga/effects";
import {QUERY_SCHEDULE_LOAD_DATA} from "../../graphql/appointments.queries";
import {INSERT_AUDIT_TRAIL} from "../../graphql/audit_trail.queries";
import client from "../../utils/GraphQLClient";
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import {
scheduleLoadFailure,
scheduleLoadSuccess,
setProblemJobs,
} from "./application.actions";
import {CalculateLoad, CheckJobBucket} from "../../utils/SSSUtils";
import {scheduleLoadFailure, scheduleLoadSuccess, setProblemJobs,} from "./application.actions";
import ApplicationActionTypes from "./application.types";
export function* onCalculateScheduleLoad() {
yield takeLatest(
ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD,
calculateScheduleLoad
);
yield takeLatest(
ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD,
calculateScheduleLoad
);
}
export function* calculateScheduleLoad({ payload: end }) {
//REMINDER: dayjs.js is not immutable. Today WILL change when adjusted.
const today = dayjs().startOf("day");
const state = yield select();
const buckets = state.user.bodyshop.ssbuckets;
try {
const result = yield client.query({
query: QUERY_SCHEDULE_LOAD_DATA,
variables: {
start: today,
end: end,
},
});
const { prodJobs, arrJobs, compJobs } = result.data;
export function* calculateScheduleLoad({payload: end}) {
//REMINDER: dayjs.js is not immutable. Today WILL change when adjusted.
const today = dayjs().startOf("day");
const state = yield select();
const buckets = state.user.bodyshop.ssbuckets;
const load = {
productionTotal: {},
productionHours: 0,
};
let problemJobs = [];
//Set the current load.
buckets.forEach((bucket) => {
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
});
prodJobs.forEach((item) => {
//Add all of the jobs currently in production to the buckets so that we have a starting point.
if (
!item.actual_completion &&
dayjs(item.scheduled_completion).isBefore(dayjs().startOf("day"))
) {
problemJobs.push({
...item,
code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections",
try {
const result = yield client.query({
query: QUERY_SCHEDULE_LOAD_DATA,
variables: {
start: today,
end: end,
},
});
}
const {prodJobs, arrJobs, compJobs} = result.data;
if (
item.actual_completion &&
dayjs(item.actual_completion).isBefore(dayjs().startOf("day"))
) {
problemJobs.push({
...item,
code: "Job is already marked as completed, but it is still in production. This job should be removed from production",
});
}
if (!(item.actual_completion || item.scheduled_completion)) {
problemJobs.push({
...item,
code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections",
});
}
const bucketId = CheckJobBucket(buckets, item);
load.productionHours =
load.productionHours +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
if (bucketId) {
load.productionTotal[bucketId].count =
load.productionTotal[bucketId].count + 1;
} else {
console.log("Uh oh, this job doesn't fit in a bucket!", item);
}
});
arrJobs.forEach((item) => {
if (!item.scheduled_in) {
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
problemJobs.push({
...item,
code: "Job has no scheduled in date",
});
}
if (!item.actual_completion && item.actual_in && !item.inproduction) {
problemJobs.push({
...item,
code: "Job has an actual in date, but no actual completion date and is not marked as in production",
});
}
if (item.actual_in && dayjs(item.actual_in).isAfter(dayjs())) {
problemJobs.push({
...item,
code: "Job has an actual in date set in the future",
});
}
if (
item.actual_completion &&
dayjs(item.actual_completion).isAfter(dayjs())
) {
problemJobs.push({
...item,
code: "Job has an actual completion date set in the future",
});
}
if (item.actual_completion && item.inproduction) {
problemJobs.push({
...item,
code: "Job has an actual completion date but it is still marked in production",
});
}
const itemDate = dayjs(item.actual_in || item.scheduled_in).format(
"YYYY-MM-DD"
);
const AddJobForSchedulingCalc = !item.inproduction;
if (!!load[itemDate]) {
load[itemDate].allHoursIn =
(load[itemDate].allHoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
//If the job hasn't already arrived, add it to the jobs in list.
// Make sure it also hasn't already been completed, or isn't an in and out job.
//This prevents the duplicate counting.
load[itemDate].allJobsIn.push(item);
if (AddJobForSchedulingCalc) {
load[itemDate].jobsIn.push(item);
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
}
} else {
load[itemDate] = {
allJobsIn: [item],
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
jobsOut: [],
allJobsOut: [],
allHoursIn:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
hoursIn: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
const load = {
productionTotal: {},
productionHours: 0,
};
}
});
let problemJobs = [];
//Set the current load.
buckets.forEach((bucket) => {
load.productionTotal[bucket.id] = {count: 0, label: bucket.label};
});
compJobs.forEach((item) => {
if (!(item.actual_completion || item.scheduled_completion))
console.warn("JOB HAS NO COMPLETION DATE.", item);
prodJobs.forEach((item) => {
//Add all of the jobs currently in production to the buckets so that we have a starting point.
if (
!item.actual_completion &&
dayjs(item.scheduled_completion).isBefore(dayjs().startOf("day"))
) {
problemJobs.push({
...item,
code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections",
});
}
const inProdJobs = prodJobs.find((p) => p.id === item.id);
const inArrJobs = arrJobs.find((p) => p.id === item.id);
if (
item.actual_completion &&
dayjs(item.actual_completion).isBefore(dayjs().startOf("day"))
) {
problemJobs.push({
...item,
code: "Job is already marked as completed, but it is still in production. This job should be removed from production",
});
}
if (!(item.actual_completion || item.scheduled_completion)) {
problemJobs.push({
...item,
code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections",
});
}
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
const bucketId = CheckJobBucket(buckets, item);
load.productionHours =
load.productionHours +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
if (bucketId) {
load.productionTotal[bucketId].count =
load.productionTotal[bucketId].count + 1;
} else {
console.log("Uh oh, this job doesn't fit in a bucket!", item);
}
});
const itemDate = dayjs(
item.actual_completion || item.scheduled_completion
).format("YYYY-MM-DD");
//Skip it, it's already completed.
arrJobs.forEach((item) => {
if (!item.scheduled_in) {
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
problemJobs.push({
...item,
code: "Job has no scheduled in date",
});
}
if (!item.actual_completion && item.actual_in && !item.inproduction) {
problemJobs.push({
...item,
code: "Job has an actual in date, but no actual completion date and is not marked as in production",
});
}
if (item.actual_in && dayjs(item.actual_in).isAfter(dayjs())) {
problemJobs.push({
...item,
code: "Job has an actual in date set in the future",
});
}
if (
item.actual_completion &&
dayjs(item.actual_completion).isAfter(dayjs())
) {
problemJobs.push({
...item,
code: "Job has an actual completion date set in the future",
});
}
if (item.actual_completion && item.inproduction) {
problemJobs.push({
...item,
code: "Job has an actual completion date but it is still marked in production",
});
}
if (!!load[itemDate]) {
load[itemDate].allHoursOut =
(load[itemDate].allHoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
//Add only the jobs that are still in production to get rid of.
//If it's not in production, we'd subtract unnecessarily.
load[itemDate].allJobsOut.push(item);
const itemDate = dayjs(item.actual_in || item.scheduled_in).format(
"YYYY-MM-DD"
);
if (AddJobForSchedulingCalc) {
load[itemDate].jobsOut.push(item);
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
const AddJobForSchedulingCalc = !item.inproduction;
if (!!load[itemDate]) {
load[itemDate].allHoursIn =
(load[itemDate].allHoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
//If the job hasn't already arrived, add it to the jobs in list.
// Make sure it also hasn't already been completed, or isn't an in and out job.
//This prevents the duplicate counting.
load[itemDate].allJobsIn.push(item);
if (AddJobForSchedulingCalc) {
load[itemDate].jobsIn.push(item);
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
}
} else {
load[itemDate] = {
allJobsIn: [item],
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
jobsOut: [],
allJobsOut: [],
allHoursIn:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
hoursIn: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
};
}
});
compJobs.forEach((item) => {
if (!(item.actual_completion || item.scheduled_completion))
console.warn("JOB HAS NO COMPLETION DATE.", item);
const inProdJobs = prodJobs.find((p) => p.id === item.id);
const inArrJobs = arrJobs.find((p) => p.id === item.id);
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
const itemDate = dayjs(
item.actual_completion || item.scheduled_completion
).format("YYYY-MM-DD");
//Skip it, it's already completed.
if (!!load[itemDate]) {
load[itemDate].allHoursOut =
(load[itemDate].allHoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
//Add only the jobs that are still in production to get rid of.
//If it's not in production, we'd subtract unnecessarily.
load[itemDate].allJobsOut.push(item);
if (AddJobForSchedulingCalc) {
load[itemDate].jobsOut.push(item);
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
}
} else {
load[itemDate] = {
allJobsOut: [item],
jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above.
hoursOut: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
allHoursOut:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
};
}
});
//Propagate the expected load to each day.
const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
for (var day = 0; day < range; day++) {
const current = dayjs(today).add(day, "day").format("YYYY-MM-DD");
const prev = dayjs(today)
.add(day - 1, "day")
.format("YYYY-MM-DD");
if (!!!load[current]) {
load[current] = {};
}
if (day === 0) {
//Starting on day 1. The load is current.
load[current].expectedLoad = CalculateLoad(
load.productionTotal,
buckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
prodJobs.length +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load.productionHours +
(load[current].hoursIn || 0) -
(load[current].hoursOut || 0);
} else {
load[current].expectedLoad = CalculateLoad(
load[prev].expectedLoad,
buckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
load[prev].expectedJobCount +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load[prev].expectedHours +
(load[current].hoursIn || 0) -
(load[current].hoursOut || 0);
}
}
} else {
load[itemDate] = {
allJobsOut: [item],
jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above.
hoursOut: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
allHoursOut:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
};
}
});
//Propagate the expected load to each day.
const range = Math.round(dayjs.duration(end.diff(today)).asDays()) + 1;
for (var day = 0; day < range; day++) {
const current = dayjs(today).add(day, "day").format("YYYY-MM-DD");
const prev = dayjs(today)
.add(day - 1, "day")
.format("YYYY-MM-DD");
if (!!!load[current]) {
load[current] = {};
}
if (day === 0) {
//Starting on day 1. The load is current.
load[current].expectedLoad = CalculateLoad(
load.productionTotal,
buckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
prodJobs.length +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load.productionHours +
(load[current].hoursIn || 0) -
(load[current].hoursOut || 0);
} else {
load[current].expectedLoad = CalculateLoad(
load[prev].expectedLoad,
buckets,
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
load[prev].expectedJobCount +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load[prev].expectedHours +
(load[current].hoursIn || 0) -
(load[current].hoursOut || 0);
}
yield put(setProblemJobs(problemJobs));
yield put(scheduleLoadSuccess(load));
} catch (error) {
yield put(scheduleLoadFailure(error));
}
yield put(setProblemJobs(problemJobs));
yield put(scheduleLoadSuccess(load));
} catch (error) {
yield put(scheduleLoadFailure(error));
}
}
export function* onInsertAuditTrail() {
yield takeLatest(
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
insertAuditTrailSaga
);
yield takeLatest(
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
insertAuditTrailSaga
);
}
export function* insertAuditTrailSaga({
payload: { jobid, billid, operation },
}) {
const state = yield select();
const bodyshop = state.user.bodyshop;
const currentUser = state.user.currentUser;
payload: {jobid, billid, operation},
}) {
const state = yield select();
const bodyshop = state.user.bodyshop;
const currentUser = state.user.currentUser;
const variables = {
auditObj: {
bodyshopid: bodyshop.id,
jobid,
billid,
operation,
useremail: currentUser.email,
},
};
yield client.mutate({
mutation: INSERT_AUDIT_TRAIL,
variables,
update(cache, { data }) {
cache.modify({
fields: {
audit_trail(existingAuditTrail, { readField }) {
const newAuditTrail = cache.writeQuery({
data: data.insert_audit_trail_one,
query: INSERT_AUDIT_TRAIL,
variables,
});
return [...existingAuditTrail, newAuditTrail];
},
const variables = {
auditObj: {
bodyshopid: bodyshop.id,
jobid,
billid,
operation,
useremail: currentUser.email,
},
});
},
});
};
yield client.mutate({
mutation: INSERT_AUDIT_TRAIL,
variables,
update(cache, {data}) {
cache.modify({
fields: {
audit_trail(existingAuditTrail, {readField}) {
const newAuditTrail = cache.writeQuery({
data: data.insert_audit_trail_one,
query: INSERT_AUDIT_TRAIL,
variables,
});
return [...existingAuditTrail, newAuditTrail];
},
},
});
},
});
}
export function* applicationSagas() {
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
}

View File

@@ -1,54 +1,54 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectApplication = (state) => state.application;
export const selectLoading = createSelector(
[selectApplication],
(application) => application.loading
[selectApplication],
(application) => application.loading
);
export const selectBreadcrumbs = createSelector(
[selectApplication],
(application) => application.breadcrumbs
[selectApplication],
(application) => application.breadcrumbs
);
export const selectRecentItems = createSelector(
[selectApplication],
(application) => application.recentItems
[selectApplication],
(application) => application.recentItems
);
export const selectScheduleLoad = createSelector(
[selectApplication],
(application) => application.scheduleLoad.load
[selectApplication],
(application) => application.scheduleLoad.load
);
export const selectPartnerVersion = createSelector(
[selectApplication],
(application) => application.partnerVersion
[selectApplication],
(application) => application.partnerVersion
);
export const selectScheduleLoadCalculating = createSelector(
[selectApplication],
(application) => application.scheduleLoad.calculating
[selectApplication],
(application) => application.scheduleLoad.calculating
);
export const selectSelectedHeader = createSelector(
[selectApplication],
(application) => application.selectedHeader
[selectApplication],
(application) => application.selectedHeader
);
export const selectJobReadOnly = createSelector(
[selectApplication],
(application) => application.jobReadOnly
[selectApplication],
(application) => application.jobReadOnly
);
export const selectOnline = createSelector(
[selectApplication],
(application) => application.online
[selectApplication],
(application) => application.online
);
export const selectProblemJobs = createSelector(
[selectApplication],
(application) => application.problemJobs
[selectApplication],
(application) => application.problemJobs
);
export const selectUpdateAvailable = createSelector(
[selectApplication],
(application) => application.updateAvailable
[selectApplication],
(application) => application.updateAvailable
);

View File

@@ -1,17 +1,17 @@
const ApplicationActionTypes = {
START_LOADING: "START_LOADING",
END_LOADING: "END_LOADING",
SET_BREAD_CRUMBS: "SET_BREAD_CRUMBS",
CALCULATE_SCHEDULE_LOAD: "CALCULATE_SCHEDULE_LOAD",
CALCULATE_SCHEDULE_LOAD_SUCCESS: "CALCULATE_SCHEDULE_LOAD_SUCCESS",
CALCULATE_SCHEDULE_LOAD_FAILURE: "CALCULATE_SCHEDULE_LOAD_FAILURE",
ADD_RECENT_ITEM: "ADD_RECENT_ITEM",
SET_SELECTED_HEADER: "SET_SELECTED_HEADER",
SET_JOB_READONLY: "SET_JOB_READONLY",
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
START_LOADING: "START_LOADING",
END_LOADING: "END_LOADING",
SET_BREAD_CRUMBS: "SET_BREAD_CRUMBS",
CALCULATE_SCHEDULE_LOAD: "CALCULATE_SCHEDULE_LOAD",
CALCULATE_SCHEDULE_LOAD_SUCCESS: "CALCULATE_SCHEDULE_LOAD_SUCCESS",
CALCULATE_SCHEDULE_LOAD_FAILURE: "CALCULATE_SCHEDULE_LOAD_FAILURE",
ADD_RECENT_ITEM: "ADD_RECENT_ITEM",
SET_SELECTED_HEADER: "SET_SELECTED_HEADER",
SET_JOB_READONLY: "SET_JOB_READONLY",
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
};
export default ApplicationActionTypes;

View File

@@ -1,24 +1,24 @@
import EmailActionTypes from "./email.types";
export const toggleEmailOverlayVisible = () => ({
type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE
type: EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE
});
export const setEmailOptions = options => ({
type: EmailActionTypes.SET_EMAIL_OPTIONS,
payload: options
type: EmailActionTypes.SET_EMAIL_OPTIONS,
payload: options
});
export const sendEmail = email => ({
type: EmailActionTypes.SEND_EMAIL,
payload: email
type: EmailActionTypes.SEND_EMAIL,
payload: email
});
export const sendEmailSuccess = options => ({
type: EmailActionTypes.SEND_EMAIL_SUCCESS
type: EmailActionTypes.SEND_EMAIL_SUCCESS
});
export const sendEmailFailure = error => ({
type: EmailActionTypes.SEND_EMAIL_FAILURE,
payload: error
type: EmailActionTypes.SEND_EMAIL_FAILURE,
payload: error
});

View File

@@ -1,35 +1,35 @@
import EmailActionTypes from "./email.types";
const INITIAL_STATE = {
emailConfig: {
messageOptions: {
from: { name: "ShopName", address: "noreply@romeonline.io" },
to: null,
replyTo: null,
emailConfig: {
messageOptions: {
from: {name: "ShopName", address: "noreply@romeonline.io"},
to: null,
replyTo: null,
},
template: {name: null, variables: {}},
},
template: { name: null, variables: {} },
},
open: false,
error: null,
open: false,
error: null,
};
const emailReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE:
return {
...state,
open: !state.open,
};
case EmailActionTypes.SET_EMAIL_OPTIONS:
return {
...state,
emailConfig: { ...action.payload },
open: true,
};
default:
return state;
}
switch (action.type) {
case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE:
return {
...state,
open: !state.open,
};
case EmailActionTypes.SET_EMAIL_OPTIONS:
return {
...state,
emailConfig: {...action.payload},
open: true,
};
default:
return state;
}
};
export default emailReducer;

View File

@@ -1,4 +1,4 @@
import { all } from "redux-saga/effects";
import {all} from "redux-saga/effects";
// import { sendEmailFailure, sendEmailSuccess } from "./email.actions";
// import { renderTemplate } from "../application/application.actions";
// import EmailActionTypes from "./email.types";
@@ -57,10 +57,10 @@ import { all } from "redux-saga/effects";
// }
export function* emailSagas() {
yield all([
// call(onSendEmail),
// call(onSendEmailFailure),
// call(onSendEmailSuccess)
//call(onSetEmailOptions),
]);
yield all([
// call(onSendEmail),
// call(onSendEmailFailure),
// call(onSendEmailSuccess)
//call(onSetEmailOptions),
]);
}

View File

@@ -1,13 +1,13 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectEmail = (state) => state.email;
export const selectEmailVisible = createSelector(
[selectEmail],
(email) => email.open
[selectEmail],
(email) => email.open
);
export const selectEmailConfig = createSelector(
[selectEmail],
(email) => email.emailConfig
[selectEmail],
(email) => email.emailConfig
);

View File

@@ -1,8 +1,8 @@
const EmailActionTypes = {
TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE",
SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS",
SEND_EMAIL: "SEND_EMAIL",
SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS",
SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE"
TOGGLE_EMAIL_OVERLAY_VISIBLE: "TOGGLE_EMAIL_OVERLAY_VISIBLE",
SET_EMAIL_OPTIONS: "SET_EMAIL_OPTIONS",
SEND_EMAIL: "SEND_EMAIL",
SEND_EMAIL_SUCCESS: "SEND_EMAIL_SUCCESS",
SEND_EMAIL_FAILURE: "SEND_EMAIL_FAILURE"
};
export default EmailActionTypes;

View File

@@ -1,43 +1,43 @@
import MediaActionTypes from "./media.types";
export const getJobMedia = (jobid) => ({
type: MediaActionTypes.GET_MEDIA_FOR_JOB,
payload: jobid,
type: MediaActionTypes.GET_MEDIA_FOR_JOB,
payload: jobid,
});
export const getBillMedia = ({ jobid, invoice_number }) => {
return {
type: MediaActionTypes.GET_MEDIA_FOR_BILL,
payload: { jobid, invoice_number },
};
export const getBillMedia = ({jobid, invoice_number}) => {
return {
type: MediaActionTypes.GET_MEDIA_FOR_BILL,
payload: {jobid, invoice_number},
};
};
export const setJobMedia = ({ jobid, media }) => ({
type: MediaActionTypes.SET_MEDIA_FOR_JOB,
payload: { jobid, media },
export const setJobMedia = ({jobid, media}) => ({
type: MediaActionTypes.SET_MEDIA_FOR_JOB,
payload: {jobid, media},
});
export const addMediaForJob = ({ jobid, media }) => ({
type: MediaActionTypes.ADD_MEDIA_FOR_JOB,
payload: { jobid, media },
export const addMediaForJob = ({jobid, media}) => ({
type: MediaActionTypes.ADD_MEDIA_FOR_JOB,
payload: {jobid, media},
});
export const getJobMediaError = ({ error, message }) => ({
type: MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR,
payload: { error, message },
export const getJobMediaError = ({error, message}) => ({
type: MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR,
payload: {error, message},
});
export const toggleMediaSelected = ({ jobid, filename }) => ({
type: MediaActionTypes.TOGGLE_MEDIA_SELECTED,
payload: { jobid, filename },
export const toggleMediaSelected = ({jobid, filename}) => ({
type: MediaActionTypes.TOGGLE_MEDIA_SELECTED,
payload: {jobid, filename},
});
export const deselectAllMediaForJob = ({ jobid }) => ({
type: MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB,
payload: { jobid },
export const deselectAllMediaForJob = ({jobid}) => ({
type: MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB,
payload: {jobid},
});
export const selectAllmediaForJob = ({ jobid }) => ({
type: MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB,
payload: { jobid },
export const selectAllmediaForJob = ({jobid}) => ({
type: MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB,
payload: {jobid},
});

View File

@@ -1,51 +1,51 @@
import MediaActionTypes from "./media.types";
const INITIAL_STATE = { error: null };
const INITIAL_STATE = {error: null};
const mediaReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MediaActionTypes.SET_MEDIA_FOR_JOB:
return { ...state, [action.payload.jobid]: action.payload.media };
case MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR:
return { ...state, error: action.payload };
case MediaActionTypes.ADD_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: [
...(state[action.payload.jobid] ? state[action.payload.jobid] : []),
...(action.payload.media || []),
],
};
case MediaActionTypes.TOGGLE_MEDIA_SELECTED:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
if (p.filename === action.payload.filename) {
p.isSelected = !p.isSelected;
}
return p;
}),
};
case MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = true;
switch (action.type) {
case MediaActionTypes.SET_MEDIA_FOR_JOB:
return {...state, [action.payload.jobid]: action.payload.media};
case MediaActionTypes.GET_MEDIA_FOR_JOB_ERROR:
return {...state, error: action.payload};
case MediaActionTypes.ADD_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: [
...(state[action.payload.jobid] ? state[action.payload.jobid] : []),
...(action.payload.media || []),
],
};
case MediaActionTypes.TOGGLE_MEDIA_SELECTED:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
if (p.filename === action.payload.filename) {
p.isSelected = !p.isSelected;
}
return p;
}),
};
case MediaActionTypes.SELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = true;
return p;
}),
};
case MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = false;
return p;
}),
};
default:
return state;
}
return p;
}),
};
case MediaActionTypes.DESELECT_ALL_MEDIA_FOR_JOB:
return {
...state,
[action.payload.jobid]: state[action.payload.jobid].map((p) => {
p.isSelected = false;
return p;
}),
};
default:
return state;
}
};
export default mediaReducer;

View File

@@ -1,120 +1,123 @@
import { all, call, takeLatest, put, select } from "redux-saga/effects";
import { getJobMediaError, setJobMedia } from "./media.actions";
import {all, call, put, select, takeLatest} from "redux-saga/effects";
import {getJobMediaError, setJobMedia} from "./media.actions";
import MediaActionTypes from "./media.types";
import cleanAxios from "../../utils/CleanAxios";
import normalizeUrl from "normalize-url";
export function* onSetJobMedia() {
yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_JOB, getJobMedia);
yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_JOB, getJobMedia);
}
export function* getJobMedia({ payload: jobid }) {
try {
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
if (localmediaserverhttp && localmediaserverhttp !== "") {
const imagesFetch = yield cleanAxios.post(
`${localmediaserverhttp}/jobs/list`,
{
jobid,
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
const documentsFetch = yield cleanAxios.post(
`${localmediaserverhttp}/bills/list`,
{
jobid,
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
export function* getJobMedia({payload: jobid}) {
try {
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
yield put(
setJobMedia({
jobid,
media: [
...imagesFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
...(d.optimized && {
optimized: normalizeUrl(
`${localmediaserverhttp}/${d.optimized}`
),
}),
isSelected: false,
key: idx,
};
}),
...documentsFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
if (localmediaserverhttp && localmediaserverhttp !== "") {
const imagesFetch = yield cleanAxios.post(
`${localmediaserverhttp}/jobs/list`,
{
jobid,
},
{headers: {ims_token: bodyshop.localmediatoken}}
);
const documentsFetch = yield cleanAxios.post(
`${localmediaserverhttp}/bills/list`,
{
jobid,
},
{headers: {ims_token: bodyshop.localmediatoken}}
);
isSelected: false,
key: idx,
};
}),
],
})
);
yield put(
setJobMedia({
jobid,
media: [
...imagesFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
...(d.optimized && {
optimized: normalizeUrl(
`${localmediaserverhttp}/${d.optimized}`
),
}),
isSelected: false,
key: idx,
};
}),
...documentsFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
isSelected: false,
key: idx,
};
}),
],
})
);
}
} catch (error) {
yield put(getJobMediaError(error));
}
} catch (error) {
yield put(getJobMediaError(error));
}
}
export function* onSetBillMedia() {
yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_BILL, getBillMedia);
yield takeLatest(MediaActionTypes.GET_MEDIA_FOR_BILL, getBillMedia);
}
export function* getBillMedia({ payload: { jobid, invoice_number } }) {
try {
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
if (localmediaserverhttp && localmediaserverhttp !== "") {
const documentsFetch = yield cleanAxios.post(
`${localmediaserverhttp}/bills/list`,
{
jobid,
invoice_number,
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
export function* getBillMedia({payload: {jobid, invoice_number}}) {
try {
const bodyshop = yield select((state) => state.user.bodyshop);
const localmediaserverhttp = bodyshop.localmediaserverhttp.trim();
yield put(
setJobMedia({
jobid,
media: [
...documentsFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
...(d.optimized && {
optimized: normalizeUrl(
`${localmediaserverhttp}/${d.optimized}`
),
}),
isSelected: false,
key: idx,
};
}),
],
})
);
if (localmediaserverhttp && localmediaserverhttp !== "") {
const documentsFetch = yield cleanAxios.post(
`${localmediaserverhttp}/bills/list`,
{
jobid,
invoice_number,
},
{headers: {ims_token: bodyshop.localmediatoken}}
);
yield put(
setJobMedia({
jobid,
media: [
...documentsFetch.data.map((d, idx) => {
return {
...d,
src: normalizeUrl(`${localmediaserverhttp}/${d.src}`),
thumbnail: normalizeUrl(
`${localmediaserverhttp}/${d.thumbnail}`
),
...(d.optimized && {
optimized: normalizeUrl(
`${localmediaserverhttp}/${d.optimized}`
),
}),
isSelected: false,
key: idx,
};
}),
],
})
);
}
} catch (error) {
yield put(getJobMediaError(error));
}
} catch (error) {
yield put(getJobMediaError(error));
}
}
export function* mediaSagas() {
yield all([call(onSetJobMedia), call(onSetBillMedia)]);
yield all([call(onSetJobMedia), call(onSetBillMedia)]);
}

View File

@@ -1,4 +1,4 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectMedia = (state) => state.media;

View File

@@ -1,11 +1,11 @@
const MediaActionTypes = {
SET_MEDIA_FOR_JOB: "SET_MEDIA_FOR_JOB",
GET_MEDIA_FOR_JOB: "GET_MEDIA_FOR_JOB",
GET_MEDIA_FOR_JOB_ERROR: "GET_MEDIA_FOR_JOB_ERROR",
ADD_MEDIA_FOR_JOB: "ADD_MEDIA_FOR_JOB",
TOGGLE_MEDIA_SELECTED: "TOGGLE_MEDIA_SELECTED",
GET_MEDIA_FOR_BILL: "GET_MEDIA_FOR_BILL",
SELECT_ALL_MEDIA_FOR_JOB: "SELECT_ALL_MEDIA_FOR_JOB",
DESELECT_ALL_MEDIA_FOR_JOB: "DESELECT_ALL_MEDIA_FOR_JOB",
SET_MEDIA_FOR_JOB: "SET_MEDIA_FOR_JOB",
GET_MEDIA_FOR_JOB: "GET_MEDIA_FOR_JOB",
GET_MEDIA_FOR_JOB_ERROR: "GET_MEDIA_FOR_JOB_ERROR",
ADD_MEDIA_FOR_JOB: "ADD_MEDIA_FOR_JOB",
TOGGLE_MEDIA_SELECTED: "TOGGLE_MEDIA_SELECTED",
GET_MEDIA_FOR_BILL: "GET_MEDIA_FOR_BILL",
SELECT_ALL_MEDIA_FOR_JOB: "SELECT_ALL_MEDIA_FOR_JOB",
DESELECT_ALL_MEDIA_FOR_JOB: "DESELECT_ALL_MEDIA_FOR_JOB",
};
export default MediaActionTypes;

View File

@@ -1,35 +1,35 @@
import MessagingActionTypes from "./messaging.types";
export const toggleChatVisible = () => ({
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
//payload: user
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
//payload: user
});
export const sendMessage = (message) => ({
type: MessagingActionTypes.SEND_MESSAGE,
payload: message,
type: MessagingActionTypes.SEND_MESSAGE,
payload: message,
});
export const sendMessageSuccess = (message) => ({
type: MessagingActionTypes.SEND_MESSAGE_SUCCESS,
payload: message,
type: MessagingActionTypes.SEND_MESSAGE_SUCCESS,
payload: message,
});
export const sendMessageFailure = (error) => ({
type: MessagingActionTypes.SEND_MESSAGE_FAILURE,
payload: error,
type: MessagingActionTypes.SEND_MESSAGE_FAILURE,
payload: error,
});
export const setSelectedConversation = (conversationId) => ({
type: MessagingActionTypes.SET_SELECTED_CONVERSATION,
payload: conversationId,
type: MessagingActionTypes.SET_SELECTED_CONVERSATION,
payload: conversationId,
});
export const openChatByPhone = (phoneNumber) => ({
type: MessagingActionTypes.OPEN_CHAT_BY_PHONE,
payload: phoneNumber,
type: MessagingActionTypes.OPEN_CHAT_BY_PHONE,
payload: phoneNumber,
});
export const setMessage = (message) => ({
type: MessagingActionTypes.SET_MESSAGE,
payload: message,
type: MessagingActionTypes.SET_MESSAGE,
payload: message,
});

View File

@@ -1,55 +1,55 @@
import MessagingActionTypes from "./messaging.types";
const INITIAL_STATE = {
open: false,
selectedConversationId: null,
isSending: false,
error: null,
message: null,
searchingForConversation: false,
open: false,
selectedConversationId: null,
isSending: false,
error: null,
message: null,
searchingForConversation: false,
};
const messagingReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MessagingActionTypes.SET_MESSAGE:
return { ...state, message: action.payload };
case MessagingActionTypes.TOGGLE_CHAT_VISIBLE:
return {
...state,
open: !state.open,
};
case MessagingActionTypes.OPEN_CHAT_BY_PHONE:
return {
...state,
searchingForConversation: true,
};
case MessagingActionTypes.SET_SELECTED_CONVERSATION:
return {
...state,
open: true,
searchingForConversation: false,
selectedConversationId: action.payload,
};
case MessagingActionTypes.SEND_MESSAGE:
return {
...state,
error: null,
isSending: true,
};
case MessagingActionTypes.SEND_MESSAGE_SUCCESS:
return {
...state,
message: "",
isSending: false,
};
case MessagingActionTypes.SEND_MESSAGE_FAILURE:
return {
...state,
error: action.payload,
};
default:
return state;
}
switch (action.type) {
case MessagingActionTypes.SET_MESSAGE:
return {...state, message: action.payload};
case MessagingActionTypes.TOGGLE_CHAT_VISIBLE:
return {
...state,
open: !state.open,
};
case MessagingActionTypes.OPEN_CHAT_BY_PHONE:
return {
...state,
searchingForConversation: true,
};
case MessagingActionTypes.SET_SELECTED_CONVERSATION:
return {
...state,
open: true,
searchingForConversation: false,
selectedConversationId: action.payload,
};
case MessagingActionTypes.SEND_MESSAGE:
return {
...state,
error: null,
isSending: true,
};
case MessagingActionTypes.SEND_MESSAGE_SUCCESS:
return {
...state,
message: "",
isSending: false,
};
case MessagingActionTypes.SEND_MESSAGE_FAILURE:
return {
...state,
error: action.payload,
};
default:
return state;
}
};
export default messagingReducer;

View File

@@ -1,112 +1,109 @@
import axios from "axios";
import parsePhoneNumber from "libphonenumber-js";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
CONVERSATION_ID_BY_PHONE,
CREATE_CONVERSATION,
} from "../../graphql/conversations.queries";
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import {all, call, put, select, takeLatest} from "redux-saga/effects";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {CONVERSATION_ID_BY_PHONE, CREATE_CONVERSATION,} from "../../graphql/conversations.queries";
import {INSERT_CONVERSATION_TAG} from "../../graphql/job-conversations.queries";
import client from "../../utils/GraphQLClient";
import { selectBodyshop } from "../user/user.selectors";
import {
sendMessageFailure,
sendMessageSuccess,
setSelectedConversation,
} from "./messaging.actions";
import {selectBodyshop} from "../user/user.selectors";
import {sendMessageFailure, sendMessageSuccess, setSelectedConversation,} from "./messaging.actions";
import MessagingActionTypes from "./messaging.types";
export function* onToggleChatVisible() {
yield takeLatest(MessagingActionTypes.TOGGLE_CHAT_VISIBLE, toggleChatLogging);
yield takeLatest(MessagingActionTypes.TOGGLE_CHAT_VISIBLE, toggleChatLogging);
}
export function* toggleChatLogging() {
try {
yield logImEXEvent("messaging_toggle_popup");
} catch (error) {
console.log("Error in sendMessage saga.", error);
}
}
export function* onOpenChatByPhone() {
yield takeLatest(MessagingActionTypes.OPEN_CHAT_BY_PHONE, openChatByPhone);
}
export function* openChatByPhone({ payload }) {
logImEXEvent("messaging_open_by_phone");
const { phone_num, jobid } = payload;
const p = parsePhoneNumber(phone_num, "CA");
const bodyshop = yield select(selectBodyshop);
try {
const {
data: { conversations },
} = yield client.query({
query: CONVERSATION_ID_BY_PHONE,
variables: { phone: p.number },
});
if (conversations.length === 0) {
const {
data: {
insert_conversations: { returning: newConversationsId },
},
} = yield client.mutate({
mutation: CREATE_CONVERSATION,
variables: {
conversation: [
{
phone_num: p.number,
bodyshopid: bodyshop.id,
job_conversations: jobid ? { data: { jobid: jobid } } : null,
},
],
},
});
yield put(setSelectedConversation(newConversationsId[0].id));
} else if (conversations.length === 1) {
//got the ID. Open it.
yield put(setSelectedConversation(conversations[0].id));
//Check to see if this job ID is already a child of it. If not add the tag.
if (
jobid &&
!conversations[0].job_conversations.find((jc) => jc.jobid === jobid)
)
yield client.mutate({
mutation: INSERT_CONVERSATION_TAG,
variables: {
conversationId: conversations[0].id,
jobId: jobid,
},
});
} else {
console.log("ERROR: Multiple conversations found. ");
yield put(setSelectedConversation(null));
try {
yield logImEXEvent("messaging_toggle_popup");
} catch (error) {
console.log("Error in sendMessage saga.", error);
}
}
export function* onOpenChatByPhone() {
yield takeLatest(MessagingActionTypes.OPEN_CHAT_BY_PHONE, openChatByPhone);
}
export function* openChatByPhone({payload}) {
logImEXEvent("messaging_open_by_phone");
const {phone_num, jobid} = payload;
const p = parsePhoneNumber(phone_num, "CA");
const bodyshop = yield select(selectBodyshop);
try {
const {
data: {conversations},
} = yield client.query({
query: CONVERSATION_ID_BY_PHONE,
variables: {phone: p.number},
});
if (conversations.length === 0) {
const {
data: {
insert_conversations: {returning: newConversationsId},
},
} = yield client.mutate({
mutation: CREATE_CONVERSATION,
variables: {
conversation: [
{
phone_num: p.number,
bodyshopid: bodyshop.id,
job_conversations: jobid ? {data: {jobid: jobid}} : null,
},
],
},
});
yield put(setSelectedConversation(newConversationsId[0].id));
} else if (conversations.length === 1) {
//got the ID. Open it.
yield put(setSelectedConversation(conversations[0].id));
//Check to see if this job ID is already a child of it. If not add the tag.
if (
jobid &&
!conversations[0].job_conversations.find((jc) => jc.jobid === jobid)
)
yield client.mutate({
mutation: INSERT_CONVERSATION_TAG,
variables: {
conversationId: conversations[0].id,
jobId: jobid,
},
});
} else {
console.log("ERROR: Multiple conversations found. ");
yield put(setSelectedConversation(null));
}
} catch (error) {
console.log("Error in sendMessage saga.", error);
}
} catch (error) {
console.log("Error in sendMessage saga.", error);
}
}
export function* onSendMessage() {
yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage);
yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage);
}
export function* sendMessage({ payload }) {
try {
const response = yield call(axios.post, "/sms/send", payload);
if (response.status === 200) {
yield put(sendMessageSuccess(payload));
} else {
yield put(sendMessageFailure(response.data));
export function* sendMessage({payload}) {
try {
const response = yield call(axios.post, "/sms/send", payload);
if (response.status === 200) {
yield put(sendMessageSuccess(payload));
} else {
yield put(sendMessageFailure(response.data));
}
} catch (error) {
console.log("Error in sendMessage saga.", error);
yield put(sendMessageFailure(error));
}
} catch (error) {
console.log("Error in sendMessage saga.", error);
yield put(sendMessageFailure(error));
}
}
export function* messagingSagas() {
yield all([
call(onSendMessage),
call(onOpenChatByPhone),
call(onToggleChatVisible),
]);
yield all([
call(onSendMessage),
call(onOpenChatByPhone),
call(onToggleChatVisible),
]);
}

View File

@@ -1,33 +1,33 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectMessaging = (state) => state.messaging;
export const selectChatVisible = createSelector(
[selectMessaging],
(messaging) => messaging.open
[selectMessaging],
(messaging) => messaging.open
);
export const selectIsSending = createSelector(
[selectMessaging],
(messaging) => messaging.isSending
[selectMessaging],
(messaging) => messaging.isSending
);
export const selectError = createSelector(
[selectMessaging],
(messaging) => messaging.error
[selectMessaging],
(messaging) => messaging.error
);
export const selectSelectedConversation = createSelector(
[selectMessaging],
(messaging) => messaging.selectedConversationId
[selectMessaging],
(messaging) => messaging.selectedConversationId
);
export const selectMessage = createSelector(
[selectMessaging],
(messaging) => messaging.message
[selectMessaging],
(messaging) => messaging.message
);
export const searchingForConversation = createSelector(
[selectMessaging],
(messaging) => messaging.searchingForConversation
[selectMessaging],
(messaging) => messaging.searchingForConversation
);

View File

@@ -1,10 +1,10 @@
const MessagingActionTypes = {
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
SEND_MESSAGE: "SEND_MESSAGE",
SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS",
SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE",
SET_SELECTED_CONVERSATION: "SET_SELECTED_CONVERSATION",
OPEN_CHAT_BY_PHONE: "OPEN_CHAT_BY_PHONE",
SET_MESSAGE: "SET_MESSAGE",
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
SEND_MESSAGE: "SEND_MESSAGE",
SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS",
SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE",
SET_SELECTED_CONVERSATION: "SET_SELECTED_CONVERSATION",
OPEN_CHAT_BY_PHONE: "OPEN_CHAT_BY_PHONE",
SET_MESSAGE: "SET_MESSAGE",
};
export default MessagingActionTypes;

View File

@@ -1,12 +1,12 @@
import ModalsActionTypes from "./modals.types";
export const toggleModalVisible = modalName => ({
type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE,
payload: modalName
type: ModalsActionTypes.TOGGLE_MODAL_VISIBLE,
payload: modalName
});
//Modal Context: {context (context object), modal(name of modal)}
export const setModalContext = modalContext => ({
type: ModalsActionTypes.SET_MODAL_CONTEXT,
payload: modalContext
type: ModalsActionTypes.SET_MODAL_CONTEXT,
payload: modalContext
});

View File

@@ -1,56 +1,56 @@
import ModalsActionTypes from "./modals.types";
const baseModal = {
open: false,
context: {},
actions: {
refetch: null,
},
open: false,
context: {},
actions: {
refetch: null,
},
};
const INITIAL_STATE = {
jobLineEdit: { ...baseModal },
billEnter: { ...baseModal },
courtesyCarReturn: { ...baseModal },
noteUpsert: { ...baseModal },
schedule: { ...baseModal },
partsOrder: { ...baseModal },
timeTicket: { ...baseModal },
timeTicketTask: { ...baseModal },
printCenter: { ...baseModal },
reconciliation: { ...baseModal },
payment: { ...baseModal },
jobCosting: { ...baseModal },
reportCenter: { ...baseModal },
partsReceive: { ...baseModal },
contractFinder: { ...baseModal },
inventoryUpsert: { ...baseModal },
ca_bc_eftTableConvert: { ...baseModal },
cardPayment: { ...baseModal },
jobLineEdit: {...baseModal},
billEnter: {...baseModal},
courtesyCarReturn: {...baseModal},
noteUpsert: {...baseModal},
schedule: {...baseModal},
partsOrder: {...baseModal},
timeTicket: {...baseModal},
timeTicketTask: {...baseModal},
printCenter: {...baseModal},
reconciliation: {...baseModal},
payment: {...baseModal},
jobCosting: {...baseModal},
reportCenter: {...baseModal},
partsReceive: {...baseModal},
contractFinder: {...baseModal},
inventoryUpsert: {...baseModal},
ca_bc_eftTableConvert: {...baseModal},
cardPayment: {...baseModal},
};
const modalsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
return {
...state,
[action.payload]: {
...state[action.payload],
open: !state[action.payload].open,
},
};
case ModalsActionTypes.SET_MODAL_CONTEXT:
return {
...state,
[action.payload.modal]: {
...state[action.payload.modal],
...action.payload.context,
open: true,
},
};
default:
return state;
}
switch (action.type) {
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
return {
...state,
[action.payload]: {
...state[action.payload],
open: !state[action.payload].open,
},
};
case ModalsActionTypes.SET_MODAL_CONTEXT:
return {
...state,
[action.payload.modal]: {
...state[action.payload.modal],
...action.payload.context,
open: true,
},
};
default:
return state;
}
};
export default modalsReducer;

View File

@@ -1,7 +1,7 @@
import { all } from "redux-saga/effects";
import {all} from "redux-saga/effects";
export function* modalsSagas() {
yield all([
//call(onSendEmail),
]);
yield all([
//call(onSendEmail),
]);
}

View File

@@ -1,90 +1,90 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectModals = (state) => state.modals;
export const selectJobLineEditModal = createSelector(
[selectModals],
(modals) => modals.jobLineEdit
[selectModals],
(modals) => modals.jobLineEdit
);
export const selectBillEnterModal = createSelector(
[selectModals],
(modals) => modals.billEnter
[selectModals],
(modals) => modals.billEnter
);
export const selectCourtesyCarReturn = createSelector(
[selectModals],
(modals) => modals.courtesyCarReturn
[selectModals],
(modals) => modals.courtesyCarReturn
);
export const selectNoteUpsert = createSelector(
[selectModals],
(modals) => modals.noteUpsert
[selectModals],
(modals) => modals.noteUpsert
);
export const selectSchedule = createSelector(
[selectModals],
(modals) => modals.schedule
[selectModals],
(modals) => modals.schedule
);
export const selectPartsOrder = createSelector(
[selectModals],
(modals) => modals.partsOrder
[selectModals],
(modals) => modals.partsOrder
);
export const selectTimeTicket = createSelector(
[selectModals],
(modals) => modals.timeTicket
[selectModals],
(modals) => modals.timeTicket
);
export const selectTimeTicketTasks = createSelector(
[selectModals],
(modals) => modals.timeTicketTask
[selectModals],
(modals) => modals.timeTicketTask
);
export const selectPrintCenter = createSelector(
[selectModals],
(modals) => modals.printCenter
[selectModals],
(modals) => modals.printCenter
);
export const selectReconciliation = createSelector(
[selectModals],
(modals) => modals.reconciliation
[selectModals],
(modals) => modals.reconciliation
);
export const selectPayment = createSelector(
[selectModals],
(modals) => modals.payment
[selectModals],
(modals) => modals.payment
);
export const selectJobCosting = createSelector(
[selectModals],
(modals) => modals.jobCosting
[selectModals],
(modals) => modals.jobCosting
);
export const selectReportCenter = createSelector(
[selectModals],
(modals) => modals.reportCenter
[selectModals],
(modals) => modals.reportCenter
);
export const selectPartsReceive = createSelector(
[selectModals],
(modals) => modals.partsReceive
[selectModals],
(modals) => modals.partsReceive
);
export const selectContractFinder = createSelector(
[selectModals],
(modals) => modals.contractFinder
[selectModals],
(modals) => modals.contractFinder
);
export const selectInventoryUpsert = createSelector(
[selectModals],
(modals) => modals.inventoryUpsert
[selectModals],
(modals) => modals.inventoryUpsert
);
export const selectCaBcEtfTableConvert = createSelector(
[selectModals],
(modals) => modals.ca_bc_eftTableConvert
[selectModals],
(modals) => modals.ca_bc_eftTableConvert
);
export const selectCardPayment = createSelector(
[selectModals],
(modals) => modals.cardPayment
[selectModals],
(modals) => modals.cardPayment
);

View File

@@ -1,5 +1,5 @@
const ModalActionTypes = {
TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT"
TOGGLE_MODAL_VISIBLE: "TOGGLE_MODAL_VISIBLE",
SET_MODAL_CONTEXT: "SET_MODAL_CONTEXT"
};
export default ModalActionTypes;

View File

@@ -1,7 +1,7 @@
import { combineReducers } from "redux";
import { persistReducer } from "redux-persist";
import {combineReducers} from "redux";
import {persistReducer} from "redux-persist";
import storage from "redux-persist/lib/storage";
import { withReduxStateSync } from "redux-state-sync";
import {withReduxStateSync} from "redux-state-sync";
import applicationReducer from "./application/application.reducer";
import emailReducer from "./email/email.reducer";
import mediaReducer from "./media/media.reducer";
@@ -18,23 +18,23 @@ import userReducer from "./user/user.reducer";
// };
const applicationPersistConfig = {
key: "v",
storage: storage,
whitelist: ["recentItems"],
key: "v",
storage: storage,
whitelist: ["recentItems"],
};
const rootReducer = combineReducers({
user: userReducer,
messaging: messagingReducer,
email: emailReducer,
modals: modalsReducer,
application: persistReducer(applicationPersistConfig, applicationReducer),
tech: techReducer,
media: mediaReducer,
user: userReducer,
messaging: messagingReducer,
email: emailReducer,
modals: modalsReducer,
application: persistReducer(applicationPersistConfig, applicationReducer),
tech: techReducer,
media: mediaReducer,
});
export default withReduxStateSync(
// persistReducer(persistConfig,
rootReducer
//)
// persistReducer(persistConfig,
rootReducer
//)
);

View File

@@ -1,21 +1,21 @@
import { all, call } from "redux-saga/effects";
import {all, call} from "redux-saga/effects";
import { userSagas } from "./user/user.sagas";
import { messagingSagas } from "./messaging/messaging.sagas";
import { emailSagas } from "./email/email.sagas";
import { modalsSagas } from "./modals/modals.sagas";
import { applicationSagas } from "./application/application.sagas";
import { techSagas } from "./tech/tech.sagas";
import { mediaSagas } from "./media/media.sagas";
import {userSagas} from "./user/user.sagas";
import {messagingSagas} from "./messaging/messaging.sagas";
import {emailSagas} from "./email/email.sagas";
import {modalsSagas} from "./modals/modals.sagas";
import {applicationSagas} from "./application/application.sagas";
import {techSagas} from "./tech/tech.sagas";
import {mediaSagas} from "./media/media.sagas";
export default function* rootSaga() {
yield all([
call(userSagas),
call(messagingSagas),
call(emailSagas),
call(modalsSagas),
call(applicationSagas),
call(techSagas),
call(mediaSagas),
]);
yield all([
call(userSagas),
call(messagingSagas),
call(emailSagas),
call(modalsSagas),
call(applicationSagas),
call(techSagas),
call(mediaSagas),
]);
}

View File

@@ -1,55 +1,50 @@
import { configureStore } from '@reduxjs/toolkit';
import {
persistStore,
} from "redux-persist";
import { createLogger } from "redux-logger";
import {configureStore} from '@reduxjs/toolkit';
import {persistStore,} from "redux-persist";
import {createLogger} from "redux-logger";
import createSagaMiddleware from "redux-saga";
import {
createStateSyncMiddleware,
initMessageListener,
} from "redux-state-sync";
import {createStateSyncMiddleware, initMessageListener,} from "redux-state-sync";
import rootReducer from "./root.reducer";
import rootSaga from "./root.saga";
import * as Sentry from "@sentry/react";
const sentryReduxEnhancer = Sentry.createReduxEnhancer({
// Optionally pass options
// Optionally pass options
});
const sagaMiddleWare = createSagaMiddleware();
const reduxSyncConfig = {
whitelist: [
"ADD_RECENT_ITEM", //"SET_SHOP_DETAILS"
],
whitelist: [
"ADD_RECENT_ITEM", //"SET_SHOP_DETAILS"
],
};
const middlewares = [
sagaMiddleWare,
createStateSyncMiddleware(reduxSyncConfig),
sagaMiddleWare,
createStateSyncMiddleware(reduxSyncConfig),
];
if (process.env.NODE_ENV === "development") {
middlewares.push(createLogger({ collapsed: true, diff: true }));
middlewares.push(createLogger({collapsed: true, diff: true}));
}
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false,
}).concat(middlewares),
// middleware: middlewares,
devTools: process.env.NODE_ENV !== 'production',
enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(sentryReduxEnhancer)
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
serializableCheck: false,
}).concat(middlewares),
// middleware: middlewares,
devTools: process.env.NODE_ENV !== 'production',
enhancers: (getDefaultEnhancers) => getDefaultEnhancers().concat(sentryReduxEnhancer)
});
sagaMiddleWare.run(rootSaga);
initMessageListener(store);
export const persistor = persistStore(store);
const e = { store, persistStore };
const e = {store, persistStore};
export default e;
if (window.Cypress) {
window.store = store;
window.store = store;
}

View File

@@ -1,20 +1,20 @@
import TechActionTypes from "./tech.types";
export const techLoginStart = ({ employeeid, pin }) => ({
type: TechActionTypes.TECH_LOGIN_START,
payload: { employeeid, pin },
export const techLoginStart = ({employeeid, pin}) => ({
type: TechActionTypes.TECH_LOGIN_START,
payload: {employeeid, pin},
});
export const techLoginSuccess = (tech) => ({
type: TechActionTypes.TECH_LOGIN_SUCCESS,
payload: tech,
type: TechActionTypes.TECH_LOGIN_SUCCESS,
payload: tech,
});
export const techLoginFailure = (error) => ({
type: TechActionTypes.TECH_LOGIN_FAILURE,
payload: error,
type: TechActionTypes.TECH_LOGIN_FAILURE,
payload: error,
});
export const techLogout = () => ({
type: TechActionTypes.TECH_LOGOUT,
type: TechActionTypes.TECH_LOGOUT,
});

View File

@@ -1,45 +1,46 @@
import TechActionTypes from "./tech.types";
const INITIAL_STATE = {
technician: null,
// technician: {
// employee_number: "101",
// first_name: "***HARDCODED",
// last_name: "IN REDUCER***",
// },
loginLoading: false,
loginError: null,
technician: null,
// technician: {
// employee_number: "101",
// first_name: "***HARDCODED",
// last_name: "IN REDUCER***",
// },
loginLoading: false,
loginError: null,
};
const applicationReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case TechActionTypes.TECH_LOGOUT:
return {
...state,
technician: null,
loginError: null,
};
case TechActionTypes.TECH_LOGIN_START:
return {
...state,
loginLoading: true,
};
case TechActionTypes.TECH_LOGIN_SUCCESS:
return {
...state,
technician: action.payload,
loginLoading: false,
loginError: false,
};
case TechActionTypes.TECH_LOGIN_FAILURE:
return {
...state,
loginError: action.payload,
loginLoading: false,
};
switch (action.type) {
case TechActionTypes.TECH_LOGOUT:
return {
...state,
technician: null,
loginError: null,
};
case TechActionTypes.TECH_LOGIN_START:
return {
...state,
loginLoading: true,
};
case TechActionTypes.TECH_LOGIN_SUCCESS:
return {
...state,
technician: action.payload,
loginLoading: false,
loginError: false,
};
case TechActionTypes.TECH_LOGIN_FAILURE:
return {
...state,
loginError: action.payload,
loginLoading: false,
};
default:
return state;
}
default:
return state;
}
};
export default applicationReducer;

View File

@@ -1,36 +1,37 @@
import axios from "axios";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../user/user.selectors";
import { techLoginFailure, techLoginSuccess } from "./tech.actions";
import {all, call, put, select, takeLatest} from "redux-saga/effects";
import {logImEXEvent} from "../../firebase/firebase.utils";
import {selectBodyshop} from "../user/user.selectors";
import {techLoginFailure, techLoginSuccess} from "./tech.actions";
import TechActionTypes from "./tech.types";
export function* onSignInStart() {
yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart);
yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart);
}
export function* signInStart({ payload: { employeeid, pin } }) {
try {
logImEXEvent("redux_tech_sign_in");
const bodyshop = yield select(selectBodyshop);
const response = yield call(axios.post, "/tech/login", {
shopid: bodyshop.id,
employeeid: employeeid,
pin: pin,
});
export function* signInStart({payload: {employeeid, pin}}) {
try {
logImEXEvent("redux_tech_sign_in");
const { valid, technician, error } = response.data;
const bodyshop = yield select(selectBodyshop);
const response = yield call(axios.post, "/tech/login", {
shopid: bodyshop.id,
employeeid: employeeid,
pin: pin,
});
if (valid) {
yield put(techLoginSuccess(technician));
} else {
yield put(techLoginFailure(error));
const {valid, technician, error} = response.data;
if (valid) {
yield put(techLoginSuccess(technician));
} else {
yield put(techLoginFailure(error));
}
} catch (error) {
yield put(techLoginFailure(error));
}
} catch (error) {
yield put(techLoginFailure(error));
}
}
export function* techSagas() {
yield all([call(onSignInStart)]);
yield all([call(onSignInStart)]);
}

View File

@@ -1,16 +1,16 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectTechReducer = (state) => state.tech;
export const selectTechnician = createSelector(
[selectTechReducer],
(application) => application.technician
[selectTechReducer],
(application) => application.technician
);
export const selectLoginError = createSelector(
[selectTechReducer],
(application) => application.loginError
[selectTechReducer],
(application) => application.loginError
);
export const selectLoginLoading = createSelector(
[selectTechReducer],
(application) => application.loginLoading
[selectTechReducer],
(application) => application.loginLoading
);

View File

@@ -1,7 +1,7 @@
const TechActionTypes = {
TECH_LOGIN_START: "TECH_LOGIN_START",
TECH_LOGIN_SUCCESS: "TECH_LOGIN_SUCCESS",
TECH_LOGIN_FAILURE: "TECH_LOGIN_FAILURE",
TECH_LOGOUT: "TECH_LOGOUT",
TECH_LOGIN_START: "TECH_LOGIN_START",
TECH_LOGIN_SUCCESS: "TECH_LOGIN_SUCCESS",
TECH_LOGIN_FAILURE: "TECH_LOGIN_FAILURE",
TECH_LOGOUT: "TECH_LOGOUT",
};
export default TechActionTypes;

View File

@@ -1,121 +1,121 @@
import UserActionTypes from "./user.types";
export const signInSuccess = (user) => ({
type: UserActionTypes.SIGN_IN_SUCCESS,
payload: user,
type: UserActionTypes.SIGN_IN_SUCCESS,
payload: user,
});
export const signInFailure = (errorMsg) => ({
type: UserActionTypes.SIGN_IN_FAILURE,
payload: errorMsg,
type: UserActionTypes.SIGN_IN_FAILURE,
payload: errorMsg,
});
export const emailSignInStart = (emailAndPassword) => ({
type: UserActionTypes.EMAIL_SIGN_IN_START,
payload: emailAndPassword,
type: UserActionTypes.EMAIL_SIGN_IN_START,
payload: emailAndPassword,
});
export const checkUserSession = () => ({
type: UserActionTypes.CHECK_USER_SESSION,
type: UserActionTypes.CHECK_USER_SESSION,
});
export const signOutStart = () => ({
type: UserActionTypes.SIGN_OUT_START,
type: UserActionTypes.SIGN_OUT_START,
});
export const signOutSuccess = () => ({
type: UserActionTypes.SIGN_OUT_SUCCESS,
type: UserActionTypes.SIGN_OUT_SUCCESS,
});
export const signOutFailure = (error) => ({
type: UserActionTypes.SIGN_OUT_FAILURE,
payload: error,
type: UserActionTypes.SIGN_OUT_FAILURE,
payload: error,
});
export const unauthorizedUser = () => ({
type: UserActionTypes.UNAUTHORIZED_USER,
type: UserActionTypes.UNAUTHORIZED_USER,
});
export const setUserLanguage = (language) => ({
type: UserActionTypes.SET_USER_LANGUAGE,
payload: language,
type: UserActionTypes.SET_USER_LANGUAGE,
payload: language,
});
export const updateUserDetails = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails,
type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails,
});
export const updateUserDetailsSuccess = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails,
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails,
});
export const setBodyshop = (bodyshop) => ({
type: UserActionTypes.SET_SHOP_DETAILS,
payload: bodyshop,
type: UserActionTypes.SET_SHOP_DETAILS,
payload: bodyshop,
});
export const setInstanceId = (userInfo) => ({
type: UserActionTypes.SET_INSTANCE_ID,
payload: userInfo,
type: UserActionTypes.SET_INSTANCE_ID,
payload: userInfo,
});
export const checkInstanceId = (uid) => ({
type: UserActionTypes.CHECK_INSTANCE_ID,
payload: uid,
type: UserActionTypes.CHECK_INSTANCE_ID,
payload: uid,
});
export const setInstanceConflict = () => ({
type: UserActionTypes.SET_INSTANCE_CONFLICT,
type: UserActionTypes.SET_INSTANCE_CONFLICT,
});
export const setLocalFingerprint = (fingerprint) => ({
type: UserActionTypes.SET_LOCAL_FINGERPRINT,
payload: fingerprint,
type: UserActionTypes.SET_LOCAL_FINGERPRINT,
payload: fingerprint,
});
export const sendPasswordReset = (email) => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
payload: email,
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
payload: email,
});
export const sendPasswordResetAgain = (email) => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
payload: email,
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
payload: email,
});
export const sendPasswordResetFailure = (error) => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE,
payload: error,
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE,
payload: error,
});
export const sendPasswordResetSuccess = () => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS,
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS,
});
export const validatePasswordResetStart = (emailAndPin) => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_START,
payload: emailAndPin,
type: UserActionTypes.VALIDATE_PASSWORD_RESET_START,
payload: emailAndPin,
});
export const validatePasswordResetSuccess = () => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS,
type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS,
});
export const validatePasswordResetFailure = (error) => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE,
payload: error,
type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE,
payload: error,
});
export const setAuthlevel = (authlevel) => ({
type: UserActionTypes.SET_AUTH_LEVEL,
payload: authlevel,
type: UserActionTypes.SET_AUTH_LEVEL,
payload: authlevel,
});
export const setCurrentEula = (eula) => ({
type: UserActionTypes.SET_CURRENT_EULA,
payload: eula,
type: UserActionTypes.SET_CURRENT_EULA,
payload: eula,
});
export const acceptEula = () => ({
type: UserActionTypes.EULA_ACCEPTED,
type: UserActionTypes.EULA_ACCEPTED,
});

View File

@@ -1,129 +1,129 @@
import UserActionTypes from "./user.types";
const INITIAL_STATE = {
currentUser: {
authorized: null,
eulaIsAccepted: false,
//language: "en-US"
},
bodyshop: null,
loginLoading: false,
fingerprint: null,
error: null,
conflict: false,
passwordreset: {
email: null,
currentUser: {
authorized: null,
eulaIsAccepted: false,
//language: "en-US"
},
bodyshop: null,
loginLoading: false,
fingerprint: null,
error: null,
success: false,
loading: false,
},
authLevel: 0,
currentEula: null,
conflict: false,
passwordreset: {
email: null,
error: null,
success: false,
loading: false,
},
authLevel: 0,
currentEula: null,
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case UserActionTypes.SET_LOCAL_FINGERPRINT:
return { ...state, fingerprint: action.payload };
case UserActionTypes.SET_INSTANCE_ID:
return { ...state, conflict: false };
case UserActionTypes.SET_INSTANCE_CONFLICT:
return { ...state, conflict: true };
case UserActionTypes.EMAIL_SIGN_IN_START:
return { ...state, loginLoading: true };
case UserActionTypes.VALIDATE_PASSWORD_RESET_START:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START:
return {
...state,
passwordreset: {
email: action.payload,
error: null,
success: false,
loading: true,
},
};
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN:
return {
...state,
passwordreset: {
email: action.payload,
error: null,
success: true,
loading: true,
},
};
case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE:
return { ...state, passwordreset: { error: action.payload } };
case UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS:
return {
...state,
passwordreset: {
...state.passwordreset,
success: true,
loading: false,
},
};
case UserActionTypes.EULA_ACCEPTED:
return {
...state,
currentUser:{...state.currentUser, eulaIsAccepted: true},
currentEula: null,
};
case UserActionTypes.SIGN_IN_SUCCESS:
const{ currentEula,...currentUser} = action.payload
return {
...state,
loginLoading: false,
currentUser: currentUser,
currentEula,
error: null,
};
case UserActionTypes.SIGN_OUT_SUCCESS:
return {
...state,
currentUser: { authorized: false },
error: null,
};
case UserActionTypes.UNAUTHORIZED_USER:
return {
...state,
error: null,
currentUser: { authorized: false },
};
case UserActionTypes.SET_USER_LANGUAGE:
return {
...state,
language: action.payload,
};
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return {
...state,
currentUser: {
...state.currentUser,
...action.payload, //Spread current user details in.
},
};
switch (action.type) {
case UserActionTypes.SET_LOCAL_FINGERPRINT:
return {...state, fingerprint: action.payload};
case UserActionTypes.SET_INSTANCE_ID:
return {...state, conflict: false};
case UserActionTypes.SET_INSTANCE_CONFLICT:
return {...state, conflict: true};
case UserActionTypes.EMAIL_SIGN_IN_START:
return {...state, loginLoading: true};
case UserActionTypes.VALIDATE_PASSWORD_RESET_START:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START:
return {
...state,
passwordreset: {
email: action.payload,
error: null,
success: false,
loading: true,
},
};
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN:
return {
...state,
passwordreset: {
email: action.payload,
error: null,
success: true,
loading: true,
},
};
case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE:
return {...state, passwordreset: {error: action.payload}};
case UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS:
return {
...state,
passwordreset: {
...state.passwordreset,
success: true,
loading: false,
},
};
case UserActionTypes.EULA_ACCEPTED:
return {
...state,
currentUser: {...state.currentUser, eulaIsAccepted: true},
currentEula: null,
};
case UserActionTypes.SIGN_IN_SUCCESS:
const {currentEula, ...currentUser} = action.payload
return {
...state,
loginLoading: false,
currentUser: currentUser,
currentEula,
error: null,
};
case UserActionTypes.SIGN_OUT_SUCCESS:
return {
...state,
currentUser: {authorized: false},
error: null,
};
case UserActionTypes.UNAUTHORIZED_USER:
return {
...state,
error: null,
currentUser: {authorized: false},
};
case UserActionTypes.SET_USER_LANGUAGE:
return {
...state,
language: action.payload,
};
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return {
...state,
currentUser: {
...state.currentUser,
...action.payload, //Spread current user details in.
},
};
case UserActionTypes.SET_SHOP_DETAILS:
return {
...state,
bodyshop: action.payload,
};
case UserActionTypes.SIGN_IN_FAILURE:
case UserActionTypes.SIGN_OUT_FAILURE:
case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
return {
...state,
loginLoading: false,
error: action.payload,
};
case UserActionTypes.SET_AUTH_LEVEL:
return { ...state, authLevel: action.payload };
default:
return state;
}
case UserActionTypes.SET_SHOP_DETAILS:
return {
...state,
bodyshop: action.payload,
};
case UserActionTypes.SIGN_IN_FAILURE:
case UserActionTypes.SIGN_OUT_FAILURE:
case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
return {
...state,
loginLoading: false,
error: action.payload,
};
case UserActionTypes.SET_AUTH_LEVEL:
return {...state, authLevel: action.payload};
default:
return state;
}
};
export default userReducer;

View File

@@ -1,46 +1,46 @@
import FingerprintJS from "@fingerprintjs/fingerprintjs";
import * as Sentry from "@sentry/browser";
import { notification } from "antd";
import {notification} from "antd";
import axios from "axios";
import { setUserId, setUserProperties } from "firebase/analytics";
import {setUserId, setUserProperties} from "firebase/analytics";
import {
checkActionCode,
confirmPasswordReset,
sendPasswordResetEmail,
signInWithEmailAndPassword,
signOut,
checkActionCode,
confirmPasswordReset,
sendPasswordResetEmail,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { doc, getDoc, setDoc } from "firebase/firestore";
import { getToken } from "firebase/messaging";
import {doc, getDoc, setDoc} from "firebase/firestore";
import {getToken} from "firebase/messaging";
import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { factory } from "../../App/App.container";
import {all, call, delay, put, select, takeLatest} from "redux-saga/effects";
import {factory} from "../../App/App.container";
import {
analytics,
auth,
firestore,
getCurrentUser,
logImEXEvent,
messaging,
updateCurrentUser,
analytics,
auth,
firestore,
getCurrentUser,
logImEXEvent,
messaging,
updateCurrentUser,
} from "../../firebase/firebase.utils";
import {
checkInstanceId,
sendPasswordResetFailure,
sendPasswordResetSuccess,
setAuthlevel,
setInstanceConflict,
setInstanceId,
setLocalFingerprint,
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess,
validatePasswordResetFailure,
validatePasswordResetSuccess,
checkInstanceId,
sendPasswordResetFailure,
sendPasswordResetSuccess,
setAuthlevel,
setInstanceConflict,
setInstanceId,
setLocalFingerprint,
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess,
validatePasswordResetFailure,
validatePasswordResetSuccess,
} from "./user.actions";
import UserActionTypes from "./user.types";
import client from "../../utils/GraphQLClient";
@@ -50,289 +50,299 @@ import day from "../../utils/day";
const fpPromise = FingerprintJS.load();
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
export function* signInWithEmail({ payload: { email, password } }) {
try {
logImEXEvent("redux_sign_in_attempt", { user: email });
const { user } = yield signInWithEmailAndPassword(auth, email, password);
export function* signInWithEmail({payload: {email, password}}) {
try {
logImEXEvent("redux_sign_in_attempt", {user: email});
yield put(
signInSuccess({
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
authorized: true,
})
);
} catch (error) {
yield put(signInFailure(error));
logImEXEvent("redux_sign_in_failure", { user: email, error });
}
const {user} = yield signInWithEmailAndPassword(auth, email, password);
yield put(
signInSuccess({
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
authorized: true,
})
);
} catch (error) {
yield put(signInFailure(error));
logImEXEvent("redux_sign_in_failure", {user: email, error});
}
}
export function* onCheckUserSession() {
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
}
export function* isUserAuthenticated() {
try {
logImEXEvent("redux_auth_check");
const user = yield getCurrentUser();
if (!user) {
yield put(unauthorizedUser());
return;
}
LogRocket.identify(user.email);
const eulaQuery = yield client.query({
query: QUERY_EULA,
variables: {
now: day()
},
});
const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0;
yield put(
signInSuccess({
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
authorized: true,
eulaIsAccepted,
currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0],
})
);
} catch (error) {
yield put(signInFailure(error));
}
}
export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
}
export function* signOutStart() {
try {
logImEXEvent("redux_sign_out");
const state = yield select();
//unsub from topic.
try {
const fcm_tokens = yield getToken(messaging);
yield call(axios.post, "/notifications/unsubscribe", {
fcm_tokens,
imexshopid: state.user.bodyshop.imexshopid,
type: "messaging",
});
} catch (error) {
console.log("No FCM token. Skipping unsubscribe.");
}
logImEXEvent("redux_auth_check");
yield signOut(auth);
yield put(signOutSuccess());
localStorage.removeItem("token");
} catch (error) {
yield put(signOutFailure(error.message));
}
const user = yield getCurrentUser();
if (!user) {
yield put(unauthorizedUser());
return;
}
LogRocket.identify(user.email);
const eulaQuery = yield client.query({
query: QUERY_EULA,
variables: {
now: day()
},
});
const eulaIsAccepted = eulaQuery.data.eulas.length > 0 && eulaQuery.data.eulas[0].eula_acceptances.length > 0;
yield put(
signInSuccess({
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
authorized: true,
eulaIsAccepted,
currentEula: eulaIsAccepted ? null : eulaQuery.data.eulas[0],
})
);
} catch (error) {
yield put(signInFailure(error));
}
}
export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
}
export function* signOutStart() {
try {
logImEXEvent("redux_sign_out");
const state = yield select();
//unsub from topic.
try {
const fcm_tokens = yield getToken(messaging);
yield call(axios.post, "/notifications/unsubscribe", {
fcm_tokens,
imexshopid: state.user.bodyshop.imexshopid,
type: "messaging",
});
} catch (error) {
console.log("No FCM token. Skipping unsubscribe.");
}
yield signOut(auth);
yield put(signOutSuccess());
localStorage.removeItem("token");
} catch (error) {
yield put(signOutFailure(error.message));
}
}
export function* onUpdateUserDetails() {
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
}
export function* updateUserDetails(userDetails) {
try {
const updatedDetails = yield updateCurrentUser(userDetails.payload);
try {
const updatedDetails = yield updateCurrentUser(userDetails.payload);
yield put(updateUserDetailsSuccess(updatedDetails));
notification.open({
type: "success",
message: i18next.t("profile.successes.updated"),
});
} catch (error) {
//yield put(signOutFailure(error.message));
}
yield put(updateUserDetailsSuccess(updatedDetails));
notification.open({
type: "success",
message: i18next.t("profile.successes.updated"),
});
} catch (error) {
//yield put(signOutFailure(error.message));
}
}
export function* onSetInstanceId() {
yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga);
yield takeLatest(UserActionTypes.SET_INSTANCE_ID, setInstanceIdSaga);
}
export function* setInstanceIdSaga({ payload: uid }) {
try {
const userInstanceRef = doc(firestore, `userInstance/${uid}`);
// Get the visitor identifier when you need it.
const fp = yield fpPromise;
const result = yield fp.get();
yield setDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint: result.visitorId,
});
export function* setInstanceIdSaga({payload: uid}) {
try {
const userInstanceRef = doc(firestore, `userInstance/${uid}`);
yield put(setLocalFingerprint(result.visitorId));
yield delay(5 * 60 * 1000);
if (process.env.NODE_ENV === "production") yield put(checkInstanceId(uid));
} catch (error) {
console.log("error", error);
}
// Get the visitor identifier when you need it.
const fp = yield fpPromise;
const result = yield fp.get();
yield setDoc(userInstanceRef, {
timestamp: new Date(),
fingerprint: result.visitorId,
});
yield put(setLocalFingerprint(result.visitorId));
yield delay(5 * 60 * 1000);
if (process.env.NODE_ENV === "production") yield put(checkInstanceId(uid));
} catch (error) {
console.log("error", error);
}
}
export function* onCheckInstanceId() {
yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga);
yield takeLatest(UserActionTypes.CHECK_INSTANCE_ID, checkInstanceIdSaga);
}
export function* checkInstanceIdSaga({ payload: uid }) {
try {
const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`));
let fingerprint = yield select((state) => state.user.fingerprint);
yield put(setInstanceConflict());
if (snapshot.data().fingerprint === fingerprint) {
yield delay(5 * 60 * 1000);
yield put(checkInstanceId(uid));
} else {
console.log("ERROR: Fingerprints do not match. Conflict detected.");
logImEXEvent("instance_confict");
yield put(setInstanceConflict());
export function* checkInstanceIdSaga({payload: uid}) {
try {
const snapshot = yield getDoc(doc(firestore, `userInstance/${uid}`));
let fingerprint = yield select((state) => state.user.fingerprint);
yield put(setInstanceConflict());
if (snapshot.data().fingerprint === fingerprint) {
yield delay(5 * 60 * 1000);
yield put(checkInstanceId(uid));
} else {
console.log("ERROR: Fingerprints do not match. Conflict detected.");
logImEXEvent("instance_confict");
yield put(setInstanceConflict());
}
} catch (error) {
console.log("error", error);
}
} catch (error) {
console.log("error", error);
}
}
export function* onSignInSuccess() {
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
}
export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
export function* signInSuccessSaga({payload}) {
LogRocket.identify(payload.email);
try {
// window.$crisp.push([
// "set",
// "user:nickname",
// [payload.displayName || payload.email],
// ]);
try {
// window.$crisp.push([
// "set",
// "user:nickname",
// [payload.displayName || payload.email],
// ]);
// window.$crisp.push(["set", "session:segments", [["rome-user"]]]);
// window.$crisp.push(["set", "session:segments", [["rome-user"]]]);
Sentry.setUser({
email: payload.email,
username: payload.displayName || payload.email,
});
} catch (error) {
console.log("Error updating Crisp settings.", error);
}
Sentry.setUser({
email: payload.email,
username: payload.displayName || payload.email,
});
} catch (error) {
console.log("Error updating Crisp settings.", error);
}
setUserId(analytics, payload.email);
setUserProperties(analytics, payload);
yield logImEXEvent("redux_sign_in_success");
setUserId(analytics, payload.email);
setUserProperties(analytics, payload);
yield logImEXEvent("redux_sign_in_success");
}
export function* onSendPasswordResetStart() {
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
sendPasswordResetEmailSaga
);
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
sendPasswordResetEmailSaga
);
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
sendPasswordResetEmailSaga
);
yield takeLatest(
UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START_AGAIN,
sendPasswordResetEmailSaga
);
}
export function* sendPasswordResetEmailSaga({ payload }) {
try {
yield sendPasswordResetEmail(auth, payload, {
url: "https://romeonline.io/passwordreset",
});
yield put(sendPasswordResetSuccess());
} catch (error) {
yield put(sendPasswordResetFailure(error.message));
}
export function* sendPasswordResetEmailSaga({payload}) {
try {
yield sendPasswordResetEmail(auth, payload, {
url: "https://romeonline.io/passwordreset",
});
yield put(sendPasswordResetSuccess());
} catch (error) {
yield put(sendPasswordResetFailure(error.message));
}
}
export function* onValidatePasswordResetStart() {
yield takeLatest(
UserActionTypes.VALIDATE_PASSWORD_RESET_START,
validatePasswordResetStart
);
yield takeLatest(
UserActionTypes.VALIDATE_PASSWORD_RESET_START,
validatePasswordResetStart
);
}
export function* validatePasswordResetStart({ payload: { password, code } }) {
try {
checkActionCode(auth, code);
yield confirmPasswordReset(auth, code, password);
yield put(validatePasswordResetSuccess());
} catch (error) {
yield put(validatePasswordResetFailure(error.message));
}
export function* validatePasswordResetStart({payload: {password, code}}) {
try {
checkActionCode(auth, code);
yield confirmPasswordReset(auth, code, password);
yield put(validatePasswordResetSuccess());
} catch (error) {
yield put(validatePasswordResetFailure(error.message));
}
}
export function* onSetShopDetails() {
yield takeLatest(
UserActionTypes.SET_SHOP_DETAILS,
SetAuthLevelFromShopDetails
);
yield takeLatest(
UserActionTypes.SET_SHOP_DETAILS,
SetAuthLevelFromShopDetails
);
}
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
export function* SetAuthLevelFromShopDetails({payload}) {
try {
//console.log("Setting shop timezone.");
// dayjs.tz.setDefault(payload.timezone);
const userEmail = yield select((state) => state.user.currentUser.email);
try {
//console.log("Setting shop timezone.");
// dayjs.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}
factory.client(payload.imexshopid);
const authRecord = payload.associations.filter(
(a) => a.useremail.toLowerCase() === userEmail.toLowerCase()
);
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));
yield put(
updateUserDetailsSuccess(
authRecord[0]
? {validemail: authRecord[0].user.validemail}
: {validemail: false}
)
);
if (payload.features.singleDeviceOnly) {
const user = yield select((state) => state.user.currentUser);
if (!(user.email.includes("@imex.") || user.email.includes("@rome.")))
yield put(setInstanceId(user.uid));
}
try {
// window.$crisp.push(["set", "user:company", [payload.shopname]]);
// if (authRecord[0] && authRecord[0].user.validemail) {
// window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
// }
} catch (error) {
console.error("Couldnt find $crisp.");
}
} catch (error) {
console.log(error);
yield put(signInFailure(error.message));
}
factory.client(payload.imexshopid);
const authRecord = payload.associations.filter(
(a) => a.useremail.toLowerCase() === userEmail.toLowerCase()
);
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));
yield put(
updateUserDetailsSuccess(
authRecord[0]
? { validemail: authRecord[0].user.validemail }
: { validemail: false }
)
);
if (payload.features.singleDeviceOnly) {
const user = yield select((state) => state.user.currentUser);
if (!(user.email.includes("@imex.") || user.email.includes("@rome.")))
yield put(setInstanceId(user.uid));
}
try {
// window.$crisp.push(["set", "user:company", [payload.shopname]]);
// if (authRecord[0] && authRecord[0].user.validemail) {
// window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
// }
} catch (error) {
console.error("Couldnt find $crisp.");
}
} catch (error) {
yield put(signInFailure(error.message));
}
}
export function* userSagas() {
yield all([
call(onEmailSignInStart),
call(onCheckUserSession),
call(onSignOutStart),
call(onUpdateUserDetails),
call(onSetInstanceId),
call(onCheckInstanceId),
call(onSignInSuccess),
call(onSendPasswordResetStart),
call(onValidatePasswordResetStart),
call(onSetShopDetails),
]);
yield all([
call(onEmailSignInStart),
call(onCheckUserSession),
call(onSignOutStart),
call(onUpdateUserDetails),
call(onSetInstanceId),
call(onCheckInstanceId),
call(onSignInSuccess),
call(onSendPasswordResetStart),
call(onValidatePasswordResetStart),
call(onSetShopDetails),
]);
}

View File

@@ -1,40 +1,40 @@
import { createSelector } from "reselect";
import {createSelector} from "reselect";
const selectUser = (state) => state.user;
export const selectCurrentUser = createSelector(
[selectUser],
(user) => user.currentUser
[selectUser],
(user) => user.currentUser
);
export const selectSignInError = createSelector(
[selectUser],
(user) => user.error
[selectUser],
(user) => user.error
);
export const selectBodyshop = createSelector(
[selectUser],
(user) => user.bodyshop
[selectUser],
(user) => user.bodyshop
);
export const selectInstanceConflict = createSelector(
[selectUser],
(user) => user.conflict
[selectUser],
(user) => user.conflict
);
export const selectPasswordReset = createSelector(
[selectUser],
(user) => user.passwordreset
[selectUser],
(user) => user.passwordreset
);
export const selectAuthLevel = createSelector(
[selectUser],
(user) => user.authLevel
[selectUser],
(user) => user.authLevel
);
export const selectLoginLoading = createSelector(
[selectUser],
(user) => user.loginLoading
[selectUser],
(user) => user.loginLoading
);
export const selectCurrentEula = createSelector(

View File

@@ -1,38 +1,38 @@
const UserActionTypes = {
SET_CURRENT_USER: "SET_CURRENT_USER",
GOOGLE_SIGN_IN_START: "GOOGLE_SIGN_IN_START",
SIGN_IN_SUCCESS: "SIGN_IN_SUCCESS",
SIGN_IN_FAILURE: "SIGN_IN_FAILURE",
EMAIL_SIGN_IN_START: "EMAIL_SIGN_IN_START",
CHECK_USER_SESSION: "CHECK_USER_SESSION",
SIGN_OUT_START: "SIGN_OUT_START",
SIGN_OUT_SUCCESS: "SIGN_OUT_SUCCESS",
SIGN_OUT_FAILURE: "SIGN_OUT_FAILURE",
EMAIL_SIGN_UP_START: "EMAIL_SIGN_UP_START",
EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS",
EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE",
UNAUTHORIZED_USER: "UNAUTHORIZED_USER",
SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS",
SET_SHOP_DETAILS: "SET_SHOP_DETAILS",
SET_INSTANCE_ID: "SET_INSTANCE_ID",
CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID",
SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT",
SET_LOCAL_FINGERPRINT: "SET_LOCAL_FINGERPRINT",
SEND_PASSWORD_RESET_EMAIL_START: "SEND_PASSWORD_RESET_EMAIL_START",
SEND_PASSWORD_RESET_EMAIL_START_AGAIN:
"SEND_PASSWORD_RESET_EMAIL_START_AGAIN",
SEND_PASSWORD_RESET_EMAIL_FAILURE: "SEND_PASSWORD_RESET_EMAIL_FAILURE",
SEND_PASSWORD_RESET_EMAIL_SUCCESS: "SEND_PASSWORD_RESET_EMAIL_SUCCESS",
VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START",
VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS",
VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE",
SET_AUTH_LEVEL: "SET_AUTH_LEVEL",
CHECK_ACTION_CODE_START: "CHECK_ACTION_CODE_START",
CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS",
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
SET_CURRENT_EULA: "SET_CURRENT_EULA",
EULA_ACCEPTED : "EULA_ACCEPTED",
SET_CURRENT_USER: "SET_CURRENT_USER",
GOOGLE_SIGN_IN_START: "GOOGLE_SIGN_IN_START",
SIGN_IN_SUCCESS: "SIGN_IN_SUCCESS",
SIGN_IN_FAILURE: "SIGN_IN_FAILURE",
EMAIL_SIGN_IN_START: "EMAIL_SIGN_IN_START",
CHECK_USER_SESSION: "CHECK_USER_SESSION",
SIGN_OUT_START: "SIGN_OUT_START",
SIGN_OUT_SUCCESS: "SIGN_OUT_SUCCESS",
SIGN_OUT_FAILURE: "SIGN_OUT_FAILURE",
EMAIL_SIGN_UP_START: "EMAIL_SIGN_UP_START",
EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS",
EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE",
UNAUTHORIZED_USER: "UNAUTHORIZED_USER",
SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS",
SET_SHOP_DETAILS: "SET_SHOP_DETAILS",
SET_INSTANCE_ID: "SET_INSTANCE_ID",
CHECK_INSTANCE_ID: "CHECK_INSTANCE_ID",
SET_INSTANCE_CONFLICT: "SET_INSTANCE_CONFLICT",
SET_LOCAL_FINGERPRINT: "SET_LOCAL_FINGERPRINT",
SEND_PASSWORD_RESET_EMAIL_START: "SEND_PASSWORD_RESET_EMAIL_START",
SEND_PASSWORD_RESET_EMAIL_START_AGAIN:
"SEND_PASSWORD_RESET_EMAIL_START_AGAIN",
SEND_PASSWORD_RESET_EMAIL_FAILURE: "SEND_PASSWORD_RESET_EMAIL_FAILURE",
SEND_PASSWORD_RESET_EMAIL_SUCCESS: "SEND_PASSWORD_RESET_EMAIL_SUCCESS",
VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START",
VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS",
VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE",
SET_AUTH_LEVEL: "SET_AUTH_LEVEL",
CHECK_ACTION_CODE_START: "CHECK_ACTION_CODE_START",
CHECK_ACTION_CODE_SUCCESS: "CHECK_ACTION_CODE_SUCCESS",
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
SET_CURRENT_EULA: "SET_CURRENT_EULA",
EULA_ACCEPTED: "EULA_ACCEPTED",
};
export default UserActionTypes;