Added first round of analytics and event tracking BOD-190

This commit is contained in:
Patrick Fic
2020-07-17 08:27:28 -07:00
parent 3f0394760a
commit a54a85b96c
73 changed files with 433 additions and 208 deletions

View File

@@ -8,6 +8,7 @@ import InvoiceExportButton from "../invoice-export-button/invoice-export-button.
import InvoiceExportAllButton from "../invoice-export-all-button/invoice-export-all-button.component";
import { DateFormatter } from "../../utils/DateFormatter";
import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AccountingPayablesTableComponent({
loading,
@@ -38,8 +39,7 @@ export default function AccountingPayablesTableComponent({
to={{
pathname: `/manage/shop/vendors`,
search: queryString.stringify({ selectedvendor: record.vendor.id }),
}}
>
}}>
{record.vendor.name}
</Link>
),
@@ -60,8 +60,7 @@ export default function AccountingPayablesTableComponent({
invoiceid: record.id,
vendorid: record.vendor.id,
}),
}}
>
}}>
{record.invoice_number}
</Link>
),
@@ -131,6 +130,7 @@ export default function AccountingPayablesTableComponent({
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
logImEXEvent("accounting_payables_table_search");
};
const dataSource = state.search
@@ -168,10 +168,10 @@ export default function AccountingPayablesTableComponent({
);
}}
dataSource={dataSource}
size="small"
size='small'
pagination={{ position: "top", pageSize: 50 }}
columns={columns}
rowKey="id"
rowKey='id'
onChange={handleTableChange}
rowSelection={{
onSelectAll: (selected, selectedRows) =>

View File

@@ -7,6 +7,7 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import { PaymentsExportAllButton } from "../payments-export-all-button/payments-export-all-button.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AccountingPayablesTableComponent({
loading,
@@ -129,6 +130,7 @@ export default function AccountingPayablesTableComponent({
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
logImEXEvent("account_payments_table_search");
};
const dataSource = state.search
@@ -166,10 +168,10 @@ export default function AccountingPayablesTableComponent({
);
}}
dataSource={dataSource}
size="small"
size='small'
pagination={{ position: "top", pageSize: 50 }}
columns={columns}
rowKey="id"
rowKey='id'
onChange={handleTableChange}
rowSelection={{
onSelectAll: (selected, selectedRows) =>

View File

@@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AccountingReceivablesTableComponent({ loading, jobs }) {
const { t } = useTranslation();
@@ -138,6 +139,7 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
logImEXEvent("accounting_receivables_search");
};
const dataSource = state.search
@@ -187,10 +189,10 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
);
}}
dataSource={dataSource}
size="small"
size='small'
pagination={{ position: "top" }}
columns={columns}
rowKey="id"
rowKey='id'
onChange={handleTableChange}
/>
</div>

View File

@@ -1,7 +1,8 @@
import { Alert } from "antd";
import { logImEXEvent } from "../../firebase/firebase.utils";
import React from "react";
export default function AlertComponent(props) {
if (props.type === "error") logImEXEvent("alert_render", { ...props });
return <Alert {...props} />;
}

View File

@@ -3,16 +3,19 @@ import AuditTrailListComponent from "./audit-trail-list.component";
import { useQuery } from "@apollo/react-hooks";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import AlertComponent from "../alert/alert.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AuditTrailListContainer({ recordId }) {
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { id: recordId },
fetchPolicy: "network-only"
fetchPolicy: "network-only",
});
logImEXEvent("audittrail_view", { recordId });
return (
<div>
{error ? (
<AlertComponent type="error" message={error.message} />
<AlertComponent type='error' message={error.message} />
) : (
<AuditTrailListComponent
loading={loading}

View File

@@ -3,7 +3,7 @@ import { Tag } from "antd";
import { Link } from "react-router-dom";
import { useMutation } from "@apollo/react-hooks";
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ChatConversationTitleTags({ jobConversations }) {
const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG);
@@ -16,6 +16,10 @@ export default function ChatConversationTitleTags({ jobConversations }) {
jobId: jobId,
},
});
logImEXEvent("messaging_remove_job_tag", {
conversationId: convId,
jobId: jobId,
});
}
};

View File

@@ -39,11 +39,7 @@ export function ChatConversationContainer({ selectedConversation }) {
0;
const handleMarkConversationAsRead = async () => {
if (
unreadCount > 0 &&
!!selectedConversation &&
!markingAsReadInProgress
) {
if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) {
setMarkingAsReadInProgress(true);
await markConversationRead();
setMarkingAsReadInProgress(false);

View File

@@ -1,17 +1,19 @@
import { Input, Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import React, { useState, useEffect, useRef } from "react";
import { Input, Spin } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { sendMessage } from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectIsSending } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
isSending: selectIsSending,
});
const mapDispatchToProps = (dispatch) => ({
sendMessage: (message) => dispatch(sendMessage(message)),
});
@@ -34,6 +36,7 @@ function ChatSendMessageComponent({
const { t } = useTranslation();
const handleEnter = () => {
logImEXEvent("messaging_send_message");
sendMessage({
to: conversation.phone_num,
body: message,

View File

@@ -6,6 +6,7 @@ import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries
import { Tag } from "antd";
import { useTranslation } from "react-i18next";
import { PlusOutlined } from "@ant-design/icons";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ChatTagRoContainer({ conversation }) {
const { t } = useTranslation();
@@ -21,6 +22,7 @@ export default function ChatTagRoContainer({ conversation }) {
);
const executeSearch = () => {
logImEXEvent("messaging_search_job_tag", { searchTerm: searchText });
if (called) refetch();
else {
loadRo();
@@ -32,6 +34,7 @@ export default function ChatTagRoContainer({ conversation }) {
});
const handleInsertTag = (value, option) => {
logImEXEvent("messaging_add_job_tag");
insertTag({ variables: { jobId: option.key } });
setVisible(false);
};

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import aamva from "../../utils/aamva";
import DataLabel from "../data-label/data-label.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ContractLicenseDecodeButton({ form }) {
const { t } = useTranslation();
@@ -12,14 +13,19 @@ export default function ContractLicenseDecodeButton({ form }) {
const [loading, setLoading] = useState(false);
const [decodedBarcode, setDecodedBarcode] = useState(null);
console.log("form", form);
const handleDecode = (e) => {
logImEXEvent("contract_license_decode");
setLoading(true);
const aamvaParse = aamva.parse(e.currentTarget.value);
console.log("AAMVA", aamvaParse);
setDecodedBarcode(aamvaParse);
setLoading(false);
};
const handleInsertForm = () => {
logImEXEvent("contract_license_decode_fill_form");
const values = {
driver_dlnumber: decodedBarcode.dl,
driver_dlexpiry: moment(
@@ -55,8 +61,7 @@ export default function ContractLicenseDecodeButton({ form }) {
okText={t("contracts.actions.senddltoform")}
onOk={handleInsertForm}
okButtonProps={{ disabled: !!!decodedBarcode }}
onCancel={handleCancel}
>
onCancel={handleCancel}>
<div>
<div>
<Input

View File

@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { MdClose } from "react-icons/md";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_DASHBOARD_DETAILS } from "../../graphql/bodyshop.queries";
import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries";
import {
@@ -37,6 +38,7 @@ const mapDispatchToProps = (dispatch) => ({
export function DashboardGridComponent({ currentUser, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_DASHBOARD_DETAILS);
console.log("DashboardGridComponent -> data", data)
const { t } = useTranslation();
const [state, setState] = useState({
layout: bodyshop.associations[0].user.dashboardlayout || [
@@ -47,6 +49,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
const handleLayoutChange = async (newLayout) => {
logImEXEvent("dashboard_change_layout");
setState({ ...state, layout: newLayout });
const result = await updateLayout({
variables: { email: currentUser.email, layout: newLayout },
@@ -62,6 +65,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
};
const handleRemoveComponent = (key) => {
logImEXEvent("dashboard_remove_component", { name: key });
const idxToRemove = state.layout.findIndex((i) => i.i === key);
const newLayout = state.layout;
newLayout.splice(idxToRemove, 1);
@@ -70,6 +75,8 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
};
const handleAddComponent = (e) => {
logImEXEvent("dashboard_add_component", { name: e });
handleLayoutChange([
...state.layout,
{
@@ -99,7 +106,6 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
))}
</Menu>
);
console.log("Dashboard Data:", data);
if (error) return <AlertComponent message={error.message} type='error' />;

View File

@@ -4,10 +4,14 @@ import Resizer from "react-image-file-resizer";
import { client } from "../../App/App.container";
import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
import i18n from "i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
export const handleUpload = (ev, context) => {
console.log("ev", ev);
logImEXEvent("document_upload", { filetype: ev.file.type });
const { onError, onSuccess, onProgress } = ev;
const { bodyshop, jobId } = context;
//If PDF, upload directly.

View File

@@ -14,6 +14,7 @@ import { EmailSettings } from "../../utils/TemplateConstants";
import RenderTemplate from "../../utils/RenderTemplate";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import EmailOverlayComponent from "./email-overlay.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
modalVisible: selectEmailVisible,
@@ -47,6 +48,8 @@ export function EmailOverlayContainer({
});
const handleOk = async () => {
logImEXEvent("email_send_from_modal");
setSending(true);
try {
await axios.post("/sendemail", messageOptions);
@@ -70,6 +73,8 @@ export function EmailOverlayContainer({
};
const render = async () => {
logImEXEvent("email_render_template", { template: emailConfig.template });
setLoading(true);
console.log("emailConfig", emailConfig);
let html = await RenderTemplate(emailConfig.template, bodyshop);
@@ -94,8 +99,7 @@ export function EmailOverlayContainer({
onCancel={() => {
toggleEmailOverlayVisible();
}}
okButtonProps={{ loading: sending }}
>
okButtonProps={{ loading: sending }}>
<LoadingSpinner loading={loading}>
<EmailOverlayComponent
handleConfigChange={handleConfigChange}
@@ -105,8 +109,7 @@ export function EmailOverlayContainer({
<button
onClick={() => {
navigator.clipboard.writeText(messageOptions.html);
}}
>
}}>
Copy HTML
</button>
</LoadingSpinner>

View File

@@ -1,6 +1,7 @@
import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react";
import { withTranslation } from "react-i18next";
import { Result, Button, Collapse, Row, Col, Space } from "antd";
import { logImEXEvent } from "../../firebase/firebase.utils";
class ErrorBoundary extends React.Component {
constructor() {
@@ -24,28 +25,29 @@ class ErrorBoundary extends React.Component {
render() {
const { t } = this.props;
const { error, info } = this.state;
if (this.state.hasErrored === true) {
logImEXEvent("error_boundary_rendered", { error, info });
return (
<div>
<Result
status="500"
status='500'
title={t("general.labels.exceptiontitle")}
subTitle={t("general.messages.exception")}
extra={
<Space>
<Button
type="primary"
type='primary'
onClick={() => {
window.location.reload();
}}
>
}}>
{t("general.actions.refresh")}
</Button>
<Button
onClick={() => {
alert("Not implemented yet.");
}}
>
}}>
{t("general.actions.submitticket")}
</Button>
</Space>

View File

@@ -2,9 +2,10 @@ import React, { Component } from "react";
import { withApollo } from "react-apollo";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { messaging } from "../../firebase/firebase.utils";
import { logImEXEvent, messaging } from "../../firebase/firebase.utils";
import { UPDATE_FCM_TOKEN } from "../../graphql/user.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
@@ -28,6 +29,7 @@ class FcmNotificationComponent extends Component {
})
.catch(function (err) {
console.log("Unable to get permission to notify.", err);
logImEXEvent("fcm_permission_denied", { message: err });
});
navigator.serviceWorker.addEventListener("message", (message) => {
const { payload } = message.data.firebaseMessaging;

View File

@@ -1,10 +1,12 @@
import React, { useState } from "react";
import { useLazyQuery } from "@apollo/react-hooks";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import { Input, AutoComplete } from "antd";
import { AutoComplete, Input } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import AlertComponent from "../alert/alert.component";
export default function GlobalSearch() {
const { t } = useTranslation();
@@ -13,6 +15,8 @@ export default function GlobalSearch() {
);
const handleSearch = (searchTerm) => {
logImEXEvent("global_search", { term: searchTerm });
if (searchTerm.length > 0)
callSearch({ variables: { search: searchTerm } });
};
@@ -129,6 +133,8 @@ export default function GlobalSearch() {
]
: [];
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<div>
<AutoComplete

View File

@@ -3,6 +3,7 @@ import React from "react";
import { connect } from "react-redux";
import { setUserLanguage } from "../../redux/user/user.actions";
import HeaderComponent from "./header.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapDispatchToProps = (dispatch) => ({
setUserLanguage: (language) => dispatch(setUserLanguage(language)),
@@ -12,8 +13,13 @@ export function HeaderContainer({ setUserLanguage }) {
const handleMenuClick = (e) => {
if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => {
if (err)
if (err) {
logImEXEvent("language_change_error", { error: err });
return console.log("Error encountered when changing languages.", err);
}
logImEXEvent("language_change", { language: e.key });
setUserLanguage(e.key);
});
}

View File

@@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_INVOICES } from "../../graphql/invoices.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -24,6 +26,8 @@ export function InvoiceExportAllButton({
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
logImEXEvent("accounting_payables_export_all");
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
@@ -117,8 +121,7 @@ export function InvoiceExportAllButton({
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
type='dashed'>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_INVOICES } from "../../graphql/invoices.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -23,6 +25,8 @@ export function InvoiceExportButton({
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
logImEXEvent("accounting_export_payable");
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
@@ -114,8 +118,7 @@ export function InvoiceExportButton({
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
type='dashed'>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -1,6 +1,9 @@
import Dinero from "dinero.js";
import { logImEXEvent } from "../../firebase/firebase.utils";
export const CalculateInvoiceTotal = (invoice) => {
logImEXEvent("invoice_calculate_total");
const {
total,

View File

@@ -4,6 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function JobEmployeeAssignmentsContainer({ job }) {
const { t } = useTranslation();
@@ -11,6 +12,7 @@ export default function JobEmployeeAssignmentsContainer({ job }) {
const handleAdd = async (assignment) => {
const { operation, employeeid } = assignment;
logImEXEvent("job_assign_employee", { operation });
let empAssignment = determineFieldName(operation);
@@ -29,7 +31,8 @@ export default function JobEmployeeAssignmentsContainer({ job }) {
}
};
const handleRemove = async (operation) => {
console.log("handleRemove -> operation", operation);
logImEXEvent("job_unassign_employee", { operation });
let empAssignment = determineFieldName(operation);
const result = await updateJob({
variables: { jobId: job.id, job: { [empAssignment]: null } },

View File

@@ -11,6 +11,7 @@ import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { selectBodyshop } from "../../../../redux/user/user.selectors";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import { logImEXEvent } from "../../../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -29,6 +30,7 @@ export function JobIntakeForm({ formItems, bodyshop }) {
const handleFinish = async (values) => {
console.log("values", values);
logImEXEvent("job_complete_intake");
const result = await intakeJob({
variables: {

View File

@@ -9,6 +9,7 @@ import { selectBodyshop } from "../../../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -21,7 +22,10 @@ const mapDispatchToProps = (dispatch) => ({
export function JobIntakeTemplateList({ bodyshop, templates }) {
const { jobId } = useParams();
const { t } = useTranslation();
//TODO SHould this be using the generic one?
const renderTemplate = async (templateKey) => {
logImEXEvent("job_intake_template_render");
const html = await RenderTemplate(
{
name: templateKey,
@@ -33,6 +37,8 @@ export function JobIntakeTemplateList({ bodyshop, templates }) {
};
const renderAllTemplates = () => {
logImEXEvent("job_intake_render_all_templates");
templates.forEach((template) => renderTemplate(template));
};

View File

@@ -5,13 +5,16 @@ import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-invoices-total.styles.scss";
export default function JobInvoiceTotals({ loading, invoices, jobTotals }) {
const { t } = useTranslation();
if (loading) return <LoadingSkeleton />;
if (!!!jobTotals)
return (
<AlertComponent type='error' message={t("jobs.errors.nofinancial")} />
);
const totals = JSON.parse(jobTotals);
let invoiceTotals = Dinero({ amount: 0 });

View File

@@ -12,6 +12,7 @@ import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ScoreboardAddButton({ job, ...otherBtnProps }) {
const { t } = useTranslation();
@@ -21,6 +22,8 @@ export default function ScoreboardAddButton({ job, ...otherBtnProps }) {
const [visibility, setVisibility] = useState(false);
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
setLoading(true);
const result = await insertScoreboardEntry({
variables: { sbInput: [{ jobid: job.id, ...values }] },

View File

@@ -1,6 +1,9 @@
import Dinero from "dinero.js";
import { logImEXEvent } from "../../firebase/firebase.utils";
export function CalculateJob(job, shoprates) {
logImEXEvent("job_calculate_total");
let ret = {
parts: CalculatePartsTotals(job.joblines),
rates: CalculateRatesTotals(job, shoprates),

View File

@@ -15,6 +15,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import JobsAvailableComponent from "./jobs-available-new.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -40,6 +41,8 @@ export function JobsAvailableContainer({
const [loadEstData, estData] = estDataLazyLoad;
const onModalOk = () => {
logImEXEvent("job_import_new");
setModalVisible(false);
setInsertLoading(true);
@@ -118,12 +121,11 @@ export function JobsAvailableContainer({
setSelectedOwner(null);
};
if (error) return <AlertComponent type="error" message={error.message} />;
if (error) return <AlertComponent type='error' message={error.message} />;
return (
<LoadingSpinner
loading={insertLoading}
message={t("jobs.labels.creating_new_job")}
>
message={t("jobs.labels.creating_new_job")}>
<JobsAvailableComponent
loading={loading}
data={data}

View File

@@ -18,6 +18,7 @@ import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import JobsAvailableSupplementComponent from "./jobs-available-supplement.component";
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -46,6 +47,8 @@ export function JobsAvailableSupplementContainer({
const importOptions = importOptionsState[0];
const onModalOk = async () => {
logImEXEvent("job_import_supplement");
setModalVisible(false);
setInsertLoading(true);
@@ -137,12 +140,11 @@ export function JobsAvailableSupplementContainer({
setSelectedJob(null);
};
if (error) return <AlertComponent type="error" message={error.message} />;
if (error) return <AlertComponent type='error' message={error.message} />;
return (
<LoadingSpinner
loading={insertLoading}
message={t("jobs.labels.creating_new_job")}
>
message={t("jobs.labels.creating_new_job")}>
<JobsAvailableSupplementComponent
loading={loading}
data={data}

View File

@@ -6,6 +6,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -19,13 +20,15 @@ export function JobsCloseLabmatAllocationButton({
allocation,
setAllocations,
bodyshop,
invoiced
invoiced,
}) {
const [visible, setVisible] = useState(false);
const [state, setState] = useState({ center: "", amount: 0 });
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_single");
const existingIndex = allocation.allocations.findIndex(
(e) => e.center === state.center
);
@@ -70,8 +73,7 @@ export function JobsCloseLabmatAllocationButton({
<Select
style={{ width: "200px" }}
value={state.center}
onSelect={(val) => setState({ ...state, center: val })}
>
onSelect={(val) => setState({ ...state, center: val })}>
{bodyshop.md_responsibility_centers.profits.map((r, idx) => (
<Option key={idx} value={r.name}>
{r.name}
@@ -88,17 +90,25 @@ export function JobsCloseLabmatAllocationButton({
<Button
onClick={handleAllocate}
disabled={
state.amount === 0 || state.center === "" || remainingAmount === 0 || invoiced
}
>
state.amount === 0 ||
state.center === "" ||
remainingAmount === 0 ||
invoiced
}>
{t("jobs.actions.allocate")}
</Button>
</div>
<div>
{visible ? (
<CloseCircleFilled onClick={() => setVisible(false)} disabled={invoiced} />
<CloseCircleFilled
onClick={() => setVisible(false)}
disabled={invoiced}
/>
) : (
<PlusCircleFilled onClick={() => setVisible(true)} disabled={invoiced}/>
<PlusCircleFilled
onClick={() => setVisible(true)}
disabled={invoiced}
/>
)}
</div>
</div>

View File

@@ -4,6 +4,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -19,6 +21,8 @@ export function JobsCloseAutoAllocate({
}) {
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
Object.keys(labmatAllocations).forEach((i) => {

View File

@@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -17,6 +19,8 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) {
const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
logImEXEvent("jobs_close_export");
setLoading(true);
let QbXmlResponse;
try {
@@ -107,8 +111,7 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) {
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
type='dashed'>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -1,12 +1,13 @@
import React, { useState } from "react";
import { Button, notification } from "antd";
import { useMutation } from "@apollo/react-hooks";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -26,6 +27,8 @@ export function JobsCloseSaveButton({
const [updateJob] = useMutation(UPDATE_JOB);
const handleSave = async () => {
logImEXEvent("jobs_close_save");
setLoading(true);
const result = await updateJob({

View File

@@ -1,12 +1,15 @@
import { notification } from "antd";
import i18n from "i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function AddToProduction(
apolloClient,
jobId,
completionCallback
) {
logImEXEvent("job_add_to_production");
//get a list of all fields on the job
apolloClient
.mutate({

View File

@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -37,9 +38,11 @@ export function JobsDetailHeaderActions({
const client = useApolloClient();
const history = useHistory();
const statusmenu = (
<Menu key="popovermenu">
<Menu key='popovermenu'>
<Menu.Item
onClick={() => {
logImEXEvent("job_header_schedule");
setScheduleContext({
actions: { refetch: refetch },
context: {
@@ -47,43 +50,41 @@ export function JobsDetailHeaderActions({
job: job,
},
});
}}
>
}}>
{t("jobs.actions.schedule")}
</Menu.Item>
<Menu.Item
key="enterpayments"
key='enterpayments'
onClick={() => {
logImEXEvent("job_header_enter_payment");
setPaymentContext({
actions: {},
context: { jobId: job.id },
});
}}
>
}}>
{t("menus.header.enterpayment")}
</Menu.Item>
<Menu.Item key="cccontract">
<Menu.Item key='cccontract'>
<Link
to={{
pathname: "/manage/courtesycars/contracts/new",
state: { jobId: job.id },
}}
>
}}>
{t("menus.jobsactions.newcccontract")}
</Link>
</Menu.Item>
<Menu.Item
key="addtoproduction"
key='addtoproduction'
disabled={!!!job.converted || !!job.inproduction}
onClick={() => AddToProduction(client, job.id, refetch)}
>
onClick={() => AddToProduction(client, job.id, refetch)}>
{t("jobs.actions.addtoproduction")}
</Menu.Item>
<Menu.Item key="duplicatejob">
<Menu.Item key='duplicatejob'>
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
okText="Yes"
cancelText="No"
okText='Yes'
cancelText='No'
onClick={(e) => e.stopPropagation()}
onConfirm={() =>
DuplicateJob(
@@ -95,30 +96,29 @@ export function JobsDetailHeaderActions({
}
)
}
getPopupContainer={(trigger) => trigger.parentNode}
>
getPopupContainer={(trigger) => trigger.parentNode}>
{t("menus.jobsactions.duplicate")}
</Popconfirm>
</Menu.Item>
<Menu.Item
key="postinvoices"
key='postinvoices'
onClick={() => {
logImEXEvent("job_header_enter_invoice");
setInvoiceEnterContext({
actions: { refetch: refetch },
context: {
job: job,
},
});
}}
>
}}>
{t("jobs.actions.postInvoices")}
</Menu.Item>
<Menu.Item key="closejob">
<Menu.Item key='closejob'>
<Link
to={{
pathname: `/manage/jobs/${job.id}/close`,
}}
>
}}>
{t("menus.jobsactions.closejob")}
</Link>
</Menu.Item>
@@ -127,11 +127,10 @@ export function JobsDetailHeaderActions({
);
return (
<Dropdown
className="imex-flex-row__margin"
className='imex-flex-row__margin'
overlay={statusmenu}
trigger={["click"]}
key="changestatus"
>
key='changestatus'>
<Button>
{t("general.labels.actions")} <DownCircleFilled />
</Button>

View File

@@ -11,6 +11,7 @@ import {
import { selectBodyshop } from "../../redux/user/user.selectors";
import { setEmailOptions } from "../../redux/email/email.actions";
import { TemplateList } from "../../utils/TemplateConstants";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser'
@@ -31,7 +32,7 @@ export function JobsDetailHeaderCsi({
const client = useApolloClient();
const handleCreateCsi = async (e) => {
console.log("e.target.key", e.key);
logImEXEvent("job_create_csi");
const questionSetResult = await client.query({
query: GET_CURRENT_QUESTIONSET_ID,

View File

@@ -2,6 +2,7 @@ import {
QUERY_ALL_JOB_FIELDS,
INSERT_NEW_JOB,
} from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function DuplicateJob(
apolloClient,
@@ -9,6 +10,8 @@ export default function DuplicateJob(
config,
completionCallback
) {
logImEXEvent("job_duplicate");
const { defaultOpenStatus } = config;
//get a list of all fields on the job
apolloClient

View File

@@ -2,11 +2,14 @@ import { Button } from "antd";
import axios from "axios";
import React from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function JobsDocumentsDownloadButton({ galleryImages }) {
const { t } = useTranslation();
const imagesToDownload = galleryImages.filter((image) => image.isSelected);
const handleDelete = () => {
const handleDownload = () => {
logImEXEvent("jobs_documents_download");
axios
.post("/media/download", {
ids: imagesToDownload.map((_) => _.key),
@@ -17,7 +20,7 @@ export default function JobsDocumentsDownloadButton({ galleryImages }) {
};
return (
<Button disabled={imagesToDownload.length < 1} onClick={handleDelete}>
<Button disabled={imagesToDownload.length < 1} onClick={handleDownload}>
{t("documents.actions.download")}
</Button>
);

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import axios from "axios";
import { useMutation } from "@apollo/react-hooks";
import { DELETE_DOCUMENT } from "../../graphql/documents.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function JobsDocumentsDeleteButton({
galleryImages,
@@ -13,6 +14,8 @@ export default function JobsDocumentsDeleteButton({
const [deleteDocument] = useMutation(DELETE_DOCUMENT);
const imagesToDelete = galleryImages.filter((image) => image.isSelected);
const handleDelete = () => {
logImEXEvent("job_documents_delete", { count: imagesToDelete.length });
imagesToDelete.forEach((image) => {
let timestamp = Math.floor(Date.now() / 1000);
let public_id = image.key;

View File

@@ -9,12 +9,13 @@ import React from "react";
import { useTranslation } from "react-i18next";
import Moment from "react-moment";
import { connect } from "react-redux";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { setModalContext } from "../../redux/modals/modals.actions";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
const mapDispatchToProps = dispatch => ({
setNoteUpsertContext: context =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" }))
const mapDispatchToProps = (dispatch) => ({
setNoteUpsertContext: (context) =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
});
export function JobNotesComponent({
@@ -23,7 +24,7 @@ export function JobNotesComponent({
refetch,
deleteNote,
jobId,
setNoteUpsertContext
setNoteUpsertContext,
}) {
const { t } = useTranslation();
@@ -40,13 +41,13 @@ export function JobNotesComponent({
) : null}
{record.private ? <EyeInvisibleFilled style={{ margin: 4 }} /> : null}
</span>
)
),
},
{
title: t("notes.fields.text"),
dataIndex: "text",
key: "text",
ellipsis: true
ellipsis: true,
},
{
@@ -58,15 +59,15 @@ export function JobNotesComponent({
sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at),
render: (text, record) => (
<span>
<Moment format="MM/DD/YYYY @ HH:mm">{record.updated_at}</Moment>
<Moment format='MM/DD/YYYY @ HH:mm'>{record.updated_at}</Moment>
</span>
)
),
},
{
title: t("notes.fields.createdby"),
dataIndex: "created_by",
key: "created_by",
width: 200
width: 200,
},
{
title: t("notes.actions.actions"),
@@ -77,18 +78,19 @@ export function JobNotesComponent({
<span>
<Button
onClick={() => {
logImEXEvent("job_note_delete");
deleteNote({
variables: {
noteId: record.id
}
}).then(r => {
noteId: record.id,
},
}).then((r) => {
refetch();
notification["success"]({
message: t("notes.successes.deleted")
message: t("notes.successes.deleted"),
});
});
}}
>
}}>
<DeleteFilled />
</Button>
<Button
@@ -97,16 +99,15 @@ export function JobNotesComponent({
actions: { refetch: refetch },
context: {
jobId: jobId,
existingNote: record
}
existingNote: record,
},
});
}}
>
}}>
<EditFilled />
</Button>
</span>
)
}
),
},
];
return (
@@ -117,18 +118,17 @@ export function JobNotesComponent({
setNoteUpsertContext({
actions: { refetch: refetch },
context: {
jobId: jobId
}
jobId: jobId,
},
});
}}
>
}}>
{t("notes.actions.new")}
</Button>
<Table
loading={loading}
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
columns={columns.map((item) => ({ ...item }))}
rowKey='id'
dataSource={data}
/>
</div>

View File

@@ -9,19 +9,20 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import NoteUpsertModalComponent from "./note-upsert-modal.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
noteUpsertModal: selectNoteUpsert
noteUpsertModal: selectNoteUpsert,
});
const mapDispatchToProps = dispatch => ({
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert"))
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
});
export function NoteUpsertModalContainer({
currentUser,
noteUpsertModal,
toggleModalVisible
toggleModalVisible,
}) {
const { t } = useTranslation();
const [insertNote] = useMutation(INSERT_NEW_NOTE);
@@ -40,32 +41,36 @@ export function NoteUpsertModalContainer({
}
}, [existingNote, form]);
const handleFinish = values => {
const handleFinish = (values) => {
if (existingNote) {
logImEXEvent("job_note_update");
updateNote({
variables: {
noteId: existingNote.id,
note: values
}
}).then(r => {
note: values,
},
}).then((r) => {
notification["success"]({
message: t("notes.successes.updated")
message: t("notes.successes.updated"),
});
});
if (refetch) refetch();
toggleModalVisible();
} else {
logImEXEvent("job_note_insert");
insertNote({
variables: {
noteInput: [
{ ...values, jobid: jobId, created_by: currentUser.email }
]
}
}).then(r => {
{ ...values, jobid: jobId, created_by: currentUser.email },
],
},
}).then((r) => {
if (refetch) refetch();
toggleModalVisible();
notification["success"]({
message: t("notes.successes.create")
message: t("notes.successes.create"),
});
});
}
@@ -82,8 +87,7 @@ export function NoteUpsertModalContainer({
onCancel={() => {
toggleModalVisible();
}}
destroyOnClose
>
destroyOnClose>
<Form form={form} onFinish={handleFinish} initialValues={existingNote}>
<NoteUpsertModalComponent />
</Form>

View File

@@ -3,15 +3,18 @@ import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/react-hooks";
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function OwnerDetailUpdateJobsComponent({
owner,
selectedJobs,
disabled
disabled,
}) {
const { t } = useTranslation();
const [updateJobs] = useMutation(UPDATE_JOBS);
const handlecClick = e => {
const handlecClick = (e) => {
logImEXEvent("owner_update_jobs", { count: selectedJobs.length });
updateJobs({
variables: {
jobIds: selectedJobs,
@@ -28,16 +31,16 @@ export default function OwnerDetailUpdateJobsComponent({
ownr_ph2: owner["ownr_ph2"],
ownr_st: owner["ownr_st"],
ownr_title: owner["ownr_title"],
ownr_zip: owner["ownr_zip"]
}
}
ownr_zip: owner["ownr_zip"],
},
},
})
.then(response => {
.then((response) => {
notification["success"]({ message: t("jobs.successes.updated") });
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("jobs.errors.updating", { error: JSON.stringify(error) })
message: t("jobs.errors.updating", { error: JSON.stringify(error) }),
});
});
};

View File

@@ -6,6 +6,7 @@ import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -26,6 +27,7 @@ export function PartsOrderLineBackorderButton({
const handleOnClick = async () => {
setLoading(true);
logImEXEvent("job_parts_backorder");
const result = await backorderLine({
variables: {

View File

@@ -23,6 +23,7 @@ import RenderTemplate, {
} from "../../utils/RenderTemplate";
import moment from "moment";
import { TemplateList } from "../../utils/TemplateConstants";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -68,6 +69,8 @@ export function PartsOrderModalContainer({
const [insertInvoice] = useMutation(INSERT_NEW_INVOICE);
const handleFinish = async (values) => {
logImEXEvent("parts_order_insert");
const insertResult = await insertPartOrder({
variables: {
po: [
@@ -109,11 +112,13 @@ export function PartsOrderModalContainer({
if (values.vendorid === bodyshop.inhousevendorid) {
console.log("Inhouse Invoice needs to be psoted.");
logImEXEvent("parts_order_inhouse_invoice");
let invoiceToPost = {
vendorid: bodyshop.inhousevendorid,
jobid: jobId,
total: 0,
invoice_number: `${jobId}`,
invoice_number: `${jobId}`,
federal_tax_rate: bodyshop.invoice_tax_rates.federal_tax_rate || 0,
state_tax_rate: bodyshop.invoice_tax_rates.state_tax_rate || 0,
local_tax_rate: bodyshop.invoice_tax_rates.local_tax_rate || 0,

View File

@@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -23,6 +25,8 @@ export function PaymentExportButton({
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
logImEXEvent("accounting_payment_export");
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
@@ -114,8 +118,7 @@ export function PaymentExportButton({
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
type='dashed'>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -19,6 +19,7 @@ import RenderTemplate, {
displayTemplateInWindow,
} from "../../utils/RenderTemplate";
import { setEmailOptions } from "../../redux/email/email.actions";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment,
@@ -69,6 +70,8 @@ function InvoiceEnterModalContainer({
try {
let stripePayment;
if (useStripe && bodyshop.stripe_acct_id) {
logImEXEvent("payment_stripe_attempt");
const secretKey = await axios.post("/stripe/payment", {
amount: Math.round(values.amount * 100),
stripe_acct_id: bodyshop.stripe_acct_id,
@@ -94,6 +97,7 @@ function InvoiceEnterModalContainer({
throw new Error();
}
}
logImEXEvent("payment_insert");
const newPayment = await insertPayment({
variables: {
@@ -168,15 +172,13 @@ function InvoiceEnterModalContainer({
okButtonProps={{
loading: loading,
}}
destroyOnClose
>
destroyOnClose>
<Form
onFinish={handleFinish}
autoComplete={"off"}
form={form}
layout="vertical"
initialValues={{ jobid: context.jobId }}
>
layout='vertical'
initialValues={{ jobid: context.jobId }}>
<PaymentForm form={form} stripeStateArr={stripeStateArr} />
</Form>
</Modal>

View File

@@ -44,7 +44,7 @@ export function PrintCenterItemComponent({
onClick={() => {
setEmailOptions({
messageOptions: {
Subject: "TODO FIX ME",
Subject: "//TODO FIX ME",
},
template: {
name: item.key,

View File

@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionBoardCard from "../production-board-kanban-card/production-board-kanban-card.component";
import { createBoardData } from "./production-board-kanban.utils.js";
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -39,6 +40,8 @@ export function ProductionBoardKanbanComponent({ data, bodyshop }) {
const client = useApolloClient();
const handleDragEnd = async (card, source, destination) => {
logImEXEvent("kanban_drag_end");
setIsMoving(true);
setBoardLanes(moveCard(boardLanes, source, destination));
const sameColumnTransfer = source.fromColumnId === destination.toColumnId;

View File

@@ -4,6 +4,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/react-hooks";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionListColumnAlert({ record }) {
const { t } = useTranslation();
@@ -11,6 +12,8 @@ export default function ProductionListColumnAlert({ record }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const handleAlertToggle = () => {
logImEXEvent("production_toggle_alert");
updateAlert({
variables: {
jobId: record.id,

View File

@@ -3,6 +3,7 @@ import { Dropdown, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionListColumnBodyPriority({ record }) {
const { t } = useTranslation();
@@ -10,6 +11,8 @@ export default function ProductionListColumnBodyPriority({ record }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSetBodyPriority = (e) => {
logImEXEvent("production_set_body_priority");
const { key } = e;
updateAlert({
variables: {

View File

@@ -3,6 +3,7 @@ import { Dropdown, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionListColumnPaintPriority({ record }) {
const { t } = useTranslation();
@@ -10,6 +11,8 @@ export default function ProductionListColumnPaintPriority({ record }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSetPaintPriority = (e) => {
logImEXEvent("production_set_paint_priority");
const { key } = e;
updateAlert({
variables: {

View File

@@ -3,6 +3,7 @@ import { Button, Input, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionListColumnProductionNote({ record }) {
const { t } = useTranslation();
@@ -16,6 +17,8 @@ export default function ProductionListColumnProductionNote({ record }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSaveNote = (e) => {
logImEXEvent("production_add_note");
setVisible(false);
updateAlert({
variables: {

View File

@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -14,6 +15,8 @@ export function ProductionListColumnStatus({ record, bodyshop }) {
const [updateJob] = useMutation(UPDATE_JOB);
const handleSetStatus = (e) => {
logImEXEvent("production_change_status");
const { key } = e;
updateJob({
variables: {

View File

@@ -6,6 +6,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { Button } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -24,6 +25,8 @@ export function ProductionListSaveConfigButton({
const { t } = useTranslation();
const handleSaveConfig = () => {
logImEXEvent("production_save_config");
updateShop({
variables: {
id: bodyshop.id,

View File

@@ -3,11 +3,14 @@ import { useMutation } from "@apollo/react-hooks";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProductionRemoveButton({ jobId }) {
const [removeJobFromProduction] = useMutation(UPDATE_JOB);
const { t } = useTranslation();
const handleRemoveFromProd = () => {
logImEXEvent("production_remove_job");
removeJobFromProduction({
variables: { jobId: jobId, job: { inproduction: false } },
}).catch((error) => {

View File

@@ -5,12 +5,13 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { updateUserDetails } from "../../redux/user/user.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
currentUser: selectCurrentUser,
});
const mapDispatchToProps = dispatch => ({
updateUserDetails: userDetails => dispatch(updateUserDetails(userDetails))
const mapDispatchToProps = (dispatch) => ({
updateUserDetails: (userDetails) => dispatch(updateUserDetails(userDetails)),
});
export default connect(
mapStateToProps,
@@ -18,10 +19,12 @@ export default connect(
)(function ProfileMyComponent({ currentUser, updateUserDetails }) {
const { t } = useTranslation();
const handleFinish = values => {
const handleFinish = (values) => {
logImEXEvent("profile_update");
updateUserDetails({
displayName: values.displayName,
photoURL: values.photoURL
photoURL: values.photoURL,
});
};
@@ -30,25 +33,23 @@ export default connect(
<Form
onFinish={handleFinish}
autoComplete={"no"}
initialValues={currentUser}
>
initialValues={currentUser}>
<Form.Item
label={t("user.fields.displayname")}
rules={[
{
required: true,
message: t("general.validation.required")
}
message: t("general.validation.required"),
},
]}
name="displayName"
>
name='displayName'>
<Input />
</Form.Item>
<Form.Item label={t("user.fields.photourl")} name="photoURL">
<Form.Item label={t("user.fields.photourl")} name='photoURL'>
<Input />
</Form.Item>
<Button type="primary" key="submit" htmlType="submit">
<Button type='primary' key='submit' htmlType='submit'>
{t("user.actions.updateprofile")}
</Button>
</Form>

View File

@@ -2,28 +2,31 @@ import React from "react";
import { useQuery, useMutation } from "@apollo/react-hooks";
import {
QUERY_ALL_ASSOCIATIONS,
UPDATE_ASSOCIATION
UPDATE_ASSOCIATION,
} from "../../graphql/associations.queries";
import AlertComponent from "../alert/alert.component";
import ProfileShopsComponent from "./profile-shops.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ProfileShopsContainer() {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ASSOCIATIONS);
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION);
const updateActiveShop = activeShopId => {
data.associations.forEach(record => {
const updateActiveShop = (activeShopId) => {
logImEXEvent("profile_change_active_shop");
data.associations.forEach((record) => {
updateAssocation({
variables: {
assocId: record.id,
assocActive: record.id === activeShopId ? true : false
}
assocActive: record.id === activeShopId ? true : false,
},
});
});
refetch();
};
if (error) return <AlertComponent type="error" message={error.message} />;
if (error) return <AlertComponent type='error' message={error.message} />;
return (
<ProfileShopsComponent
loading={loading}

View File

@@ -7,6 +7,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_APPOINTMENT } from "../../graphql/appointments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -22,10 +23,10 @@ export function ScheduleBlockDay({ date, children, refetch, bodyshop }) {
const handleMenu = async (e) => {
e.domEvent.stopPropagation();
console.log("date", date);
if (e.key === "block") {
console.log("Block.");
const blockAppt = {
title: t("appointments.labels.blocked"),
@@ -35,7 +36,8 @@ export function ScheduleBlockDay({ date, children, refetch, bodyshop }) {
start: moment(date).startOf("day"),
end: moment(date).endOf("day"),
};
console.log("handleMenu -> blockAppt", blockAppt);
logImEXEvent("dashboard_change_layout");
const result = await insertBlock({
variables: { app: [blockAppt] },

View File

@@ -5,13 +5,15 @@ import { UPDATE_JOB } from "../../graphql/jobs.queries";
import ScheduleEventComponent from "./schedule-event.component";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ScheduleEventContainer({ event, refetch }) {
const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB);
const handleCancel = async (id) => {
logImEXEvent("schedule_cancel_appt");
const cancelAppt = await cancelAppointment({
variables: { appid: event.id },
});

View File

@@ -17,6 +17,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectSchedule } from "../../redux/modals/modals.selectors";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { TemplateList } from "../../utils/TemplateConstants";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -64,6 +65,8 @@ export function ScheduleJobModalContainer({
});
const handleOk = async () => {
logImEXEvent("schedule_new_appointment");
setLoading(true);
if (!!previousEvent) {
const cancelAppt = await cancelAppointment({

View File

@@ -4,12 +4,15 @@ import { Button, notification } from "antd";
import { DeleteFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/react-hooks";
import { DELETE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ScoreboardRemoveButton({ scoreboardId }) {
const { t } = useTranslation();
const [deleteScoreboardEntry] = useMutation(DELETE_SCOREBOARD_ENTRY);
const [loading, setLoading] = useState(false);
const handleDelete = async (e) => {
logImEXEvent("scoreboard_remove_job");
e.stopPropagation();
setLoading(true);
const result = await deleteScoreboardEntry({

View File

@@ -13,6 +13,7 @@ import {
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import ShopEmployeeComponent from "./shop-employees.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -30,6 +31,8 @@ function ShopEmployeesContainer({ bodyshop }) {
const [insertEmployees] = useMutation(INSERT_EMPLOYEES);
const [deleteEmployee] = useMutation(DELETE_EMPLOYEE);
const handleDelete = (id) => {
logImEXEvent("shop_employee_delete");
deleteEmployee({ variables: { id: id } })
.then((r) => {
notification["success"]({
@@ -52,6 +55,8 @@ function ShopEmployeesContainer({ bodyshop }) {
console.log("values", values);
if (employeeState[0].id) {
//Update a record.
logImEXEvent("shop_employee_update");
updateEmployee({
variables: { id: employeeState[0].id, employee: values },
})
@@ -72,6 +77,8 @@ function ShopEmployeesContainer({ bodyshop }) {
});
} else {
//New record, insert it.
logImEXEvent("shop_employee_insert");
insertEmployees({
variables: { employees: [{ ...values, shopid: bodyshop.id }] },
}).then((r) => {

View File

@@ -6,6 +6,8 @@ import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ShopInfoContainer() {
const [form] = Form.useForm();
const { t } = useTranslation();
@@ -16,6 +18,7 @@ export default function ShopInfoContainer() {
const handleFinish = values => {
console.log("values", values);
logImEXEvent("shop_update");
updateBodyshop({
variables: { id: data.bodyshops[0].id, shop: values }

View File

@@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect";
import { INSERT_TEMPLATE } from "../../graphql/templates.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -34,6 +35,8 @@ export function ShopTemplateAddComponent({
);
const handleAdd = async (item) => {
logImEXEvent("shop_template_add");
const result = await insertTemplate({
variables: {
template: {

View File

@@ -5,6 +5,7 @@ import React from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation } from "react-router-dom";
import { DELETE_TEMPLATE } from "../../graphql/templates.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ShopTemplateDeleteComponent({ templateId, refetch }) {
const { t } = useTranslation();
@@ -13,6 +14,8 @@ export default function ShopTemplateDeleteComponent({ templateId, refetch }) {
const [deleteTemplate] = useMutation(DELETE_TEMPLATE);
const handleDelete = async () => {
logImEXEvent("shop_template_delete");
const result = await deleteTemplate({
variables: {
templateId: templateId,

View File

@@ -3,12 +3,15 @@ import { Button, notification } from "antd";
import { useMutation } from "@apollo/react-hooks";
import { useTranslation } from "react-i18next";
import { UPDATE_TEMPLATE } from "../../graphql/templates.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ShopTemplateSaveButton({ templateId, html, gql }) {
const { t } = useTranslation();
const [updateTemplate] = useMutation(UPDATE_TEMPLATE);
const handleSave = async () => {
logImEXEvent("shop_template_update");
const result = await updateTemplate({
variables: {
templateId: templateId,

View File

@@ -17,6 +17,8 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
@@ -37,6 +39,8 @@ export function TechClockOffButton({
const { t } = useTranslation();
const handleFinish = async (values) => {
logImEXEvent("tech_clock_out_job");
setLoading(true);
const result = await updateTimeticket({
variables: {

View File

@@ -4,6 +4,7 @@ import { DeleteFilled } from "@ant-design/icons";
import { DELETE_TIME_TICKET } from "../../graphql/timetickets.queries";
import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/react-hooks";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function TechJobClockoutDelete({
timeTicketId,
@@ -12,6 +13,8 @@ export default function TechJobClockoutDelete({
const [deleteTimeTicket] = useMutation(DELETE_TIME_TICKET);
const { t } = useTranslation();
const handleDelete = async () => {
logImEXEvent("tech_clock_delete");
const result = await deleteTimeTicket({
variables: { id: timeTicketId },
refetchQueries: ["QUERY_ACTIVE_TIME_TICKETS"],

View File

@@ -3,15 +3,18 @@ import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/react-hooks";
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function VehicleDetailUpdateJobsComponent({
vehicle,
selectedJobs,
disabled
disabled,
}) {
const { t } = useTranslation();
const [updateJobs] = useMutation(UPDATE_JOBS);
const handlecClick = e => {
const handlecClick = (e) => {
logImEXEvent("vehicle_update_jobs");
updateJobs({
variables: {
jobIds: selectedJobs,
@@ -22,16 +25,16 @@ export default function VehicleDetailUpdateJobsComponent({
v_model_yr: vehicle["v_model_yr"],
v_model_desc: vehicle["v_model_desc"],
v_make_desc: vehicle["v_make_desc"],
v_color: vehicle["v_color"]
}
}
v_color: vehicle["v_color"],
},
},
})
.then(response => {
.then((response) => {
notification["success"]({ message: t("jobs.successes.updated") });
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("jobs.errors.updating", { error: JSON.stringify(error) })
message: t("jobs.errors.updating", { error: JSON.stringify(error) }),
});
});
};

View File

@@ -47,7 +47,7 @@ try {
export { messaging };
export const logImEXEvent = async (eventName, additionalParams) => {
export const logImEXEvent = (eventName, additionalParams) => {
const state = store.getState();
const eventParams = {
shop:

View File

@@ -2,23 +2,35 @@ import axios from "axios";
import phone from "phone";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { client } from "../../App/App.container";
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 { selectBodyshop } from "../user/user.selectors";
import {
sendMessageFailure,
sendMessageSuccess,
setSelectedConversation,
} from "./messaging.actions";
import MessagingActionTypes from "./messaging.types";
import { selectBodyshop } from "../user/user.selectors";
export function* onToggleChatVisible() {
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 bodyshop = yield select(selectBodyshop);
try {
@@ -69,6 +81,8 @@ export function* onSendMessage() {
}
export function* sendMessage({ payload }) {
try {
yield logImEXEvent("messaging_send_message");
const response = yield call(axios.post, "/sms/send", payload);
if (response.status === 200) {
yield put(sendMessageSuccess(payload));
@@ -82,5 +96,9 @@ export function* sendMessage({ payload }) {
}
export function* messagingSagas() {
yield all([call(onSendMessage), call(onOpenChatByPhone)]);
yield all([
call(onSendMessage),
call(onOpenChatByPhone),
call(onToggleChatVisible),
]);
}

View File

@@ -1,4 +1,5 @@
import ModalsActionTypes from "./modals.types";
import { logImEXEvent } from "../../firebase/firebase.utils";
const baseModal = {
visible: false,
@@ -24,6 +25,7 @@ const INITIAL_STATE = {
const modalsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ModalsActionTypes.TOGGLE_MODAL_VISIBLE:
logImEXEvent("redux_toggle_modal_visible", { modal: action.payload });
return {
...state,
[action.payload]: {
@@ -32,6 +34,8 @@ const modalsReducer = (state = INITIAL_STATE, action) => {
},
};
case ModalsActionTypes.SET_MODAL_CONTEXT:
logImEXEvent("redux_set_modal_context", { modal: action.payload.modal });
return {
...state,
[action.payload.modal]: {

View File

@@ -3,30 +3,25 @@ import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { selectBodyshop } from "../user/user.selectors";
import { techLoginFailure, techLoginSuccess } from "./tech.actions";
import TechActionTypes from "./tech.types";
import { logImEXEvent } from "../../firebase/firebase.utils";
export function* onSignInStart() {
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,
});
console.log("response", response);
const { valid, technician, error } = response.data;
console.log(
"function*signInStart -> valid, technician, erro",
valid,
technician,
error
);
if (valid) {
console.log("Valid in else");
yield put(techLoginSuccess(technician));
} else {
yield put(techLoginFailure(error));

View File

@@ -1,24 +1,24 @@
import { all, call, put, takeLatest, delay, select } from "redux-saga/effects";
import LogRocket from "logrocket";
import { firestore } from "../../firebase/firebase.utils";
import Fingerprint2 from "fingerprintjs2";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import {
auth,
firestore,
getCurrentUser,
logImEXEvent,
updateCurrentUser,
} from "../../firebase/firebase.utils";
import {
checkInstanceId,
setInstanceConflict,
setInstanceId,
setLocalFingerprint,
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess,
setInstanceId,
checkInstanceId,
setLocalFingerprint,
setInstanceConflict,
} from "./user.actions";
import UserActionTypes from "./user.types";
@@ -27,6 +27,8 @@ export function* onEmailSignInStart() {
}
export function* signInWithEmail({ payload: { email, password } }) {
try {
logImEXEvent("redux_sign_in_attempt", { user: email });
const { user } = yield auth.signInWithEmailAndPassword(email, password);
yield put(
@@ -40,6 +42,7 @@ export function* signInWithEmail({ payload: { email, password } }) {
);
} catch (error) {
yield put(signInFailure(error));
logImEXEvent("redux_sign_in_failure", { user: email, error });
}
}
@@ -48,6 +51,8 @@ export function* onCheckUserSession() {
}
export function* isUserAuthenticated() {
try {
logImEXEvent("redux_auth_check");
const user = yield getCurrentUser();
if (!user) {
yield put(unauthorizedUser());
@@ -73,6 +78,8 @@ export function* onSignOutStart() {
}
export function* signOutStart() {
try {
logImEXEvent("redux_sign_out");
yield auth.signOut();
yield put(signOutSuccess());
localStorage.removeItem("token");
@@ -135,6 +142,7 @@ export function* checkInstanceIdSaga({ payload: uid }) {
yield put(checkInstanceId(uid));
} else {
console.log("ERROR: Fingerprints do not match. Conflict detected.");
logImEXEvent("instance_confict");
yield put(setInstanceConflict());
}
} catch (error) {
@@ -150,6 +158,7 @@ export function* onSignInSuccess() {
export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
yield put(setInstanceId(payload.uid));
yield logImEXEvent("redux_sign_in_success");
}
export function* userSagas() {