feat: Add Meta WAP Credentials management
- Introduced Meta WAP Credentials model and interface for managing WhatsApp Business credentials. - Implemented Mongoose adapter for Meta WAP Credentials with upsert and find functionalities. - Created service layer for handling Meta WAP Credentials operations including save, find, test, and disable. - Added API controllers for Meta WAP Credentials with endpoints for saving, finding, testing, and disabling credentials. - Updated Notifications Manager to utilize Meta WAP Credentials for sending WhatsApp messages. - Enhanced the front-end to manage Meta WAP Credentials with forms for input and validation. - Implemented UI components for displaying and interacting with Meta WAP Credentials.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
export enum META_WAP_CREDENTIAL_STATUS {
|
||||
CONFIGURED = "CONFIGURED",
|
||||
TESTED = "TESTED",
|
||||
INVALID = "INVALID",
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export type SaveMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
phoneNumberId: string;
|
||||
wabaId: string;
|
||||
accessToken: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type FindMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type TestMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
phoneNumber: string;
|
||||
templateName: string;
|
||||
languageCode: string;
|
||||
templateVariables?: string[];
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type DisableMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export interface IMetaWapCredential {
|
||||
id?: string;
|
||||
companyId: string;
|
||||
phoneNumberId: string;
|
||||
wabaId: string;
|
||||
encryptionKeyVersion?: string;
|
||||
status: META_WAP_CREDENTIAL_STATUS;
|
||||
lastTestedAt?: Date;
|
||||
lastTestError?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export type MetaWapCredentialView = IMetaWapCredential;
|
||||
|
||||
export type TestMetaWapCredentialResult = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
testedAt: Date;
|
||||
};
|
||||
|
||||
export const META_WAP_CREDENTIAL_NOT_CONFIGURED_MESSAGE =
|
||||
"La organización no tiene credenciales de Meta configuradas";
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import ApiRequest from "@services/Api.Service";
|
||||
import * as Yup from "yup";
|
||||
import { ApiError } from "@models/Server.Error.model";
|
||||
import {
|
||||
DisableMetaWapCredentialParams,
|
||||
FindMetaWapCredentialParams,
|
||||
MetaWapCredentialView,
|
||||
SaveMetaWapCredentialParams,
|
||||
TestMetaWapCredentialParams,
|
||||
TestMetaWapCredentialResult,
|
||||
} from "@models/MetaWapCredential.model";
|
||||
|
||||
const baseFields = {
|
||||
companyId: Yup.string().required("No se ha especificado una organización."),
|
||||
sessionUser: Yup.string().required(
|
||||
"No se ha podido determinar el usuario que intenta realizar esta acción"
|
||||
),
|
||||
};
|
||||
|
||||
const saveSchema = Yup.object().shape({
|
||||
...baseFields,
|
||||
phoneNumberId: Yup.string().required("Debe introducir el Phone Number ID."),
|
||||
wabaId: Yup.string().required("Debe introducir el WABA ID."),
|
||||
accessToken: Yup.string().required("Debe introducir el Access Token."),
|
||||
});
|
||||
|
||||
const findSchema = Yup.object().shape({
|
||||
...baseFields,
|
||||
});
|
||||
|
||||
const testSchema = Yup.object().shape({
|
||||
...baseFields,
|
||||
phoneNumber: Yup.string().required("Debe introducir el número de teléfono de destino."),
|
||||
templateName: Yup.string().required("Debe introducir el nombre del template."),
|
||||
languageCode: Yup.string().required("Debe introducir el código de idioma."),
|
||||
});
|
||||
|
||||
const disableSchema = Yup.object().shape({
|
||||
...baseFields,
|
||||
});
|
||||
|
||||
const validateOrThrow = async (
|
||||
validationSchema: Yup.ObjectSchema<Yup.AnyObject>,
|
||||
data: unknown
|
||||
): Promise<void> => {
|
||||
let yupErrors: Yup.ValidationError = new Yup.ValidationError("");
|
||||
|
||||
try {
|
||||
await validationSchema.validate(data, { abortEarly: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
if (error instanceof Yup.ValidationError) {
|
||||
yupErrors = error as Yup.ValidationError;
|
||||
}
|
||||
throw new ApiError(400, yupErrors.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const metaWapCredentialSave = async (
|
||||
data: SaveMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView> => {
|
||||
await validateOrThrow(saveSchema, data);
|
||||
|
||||
return ApiRequest.post<MetaWapCredentialView>("meta-credentials/save", data);
|
||||
};
|
||||
|
||||
export const metaWapCredentialFind = async (
|
||||
data: FindMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView> => {
|
||||
await validateOrThrow(findSchema, data);
|
||||
|
||||
return ApiRequest.post<MetaWapCredentialView>("meta-credentials/find", data);
|
||||
};
|
||||
|
||||
export const metaWapCredentialTest = async (
|
||||
data: TestMetaWapCredentialParams
|
||||
): Promise<TestMetaWapCredentialResult> => {
|
||||
await validateOrThrow(testSchema, data);
|
||||
|
||||
return ApiRequest.post<TestMetaWapCredentialResult>("meta-credentials/test", data);
|
||||
};
|
||||
|
||||
export const metaWapCredentialDisable = async (
|
||||
data: DisableMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView> => {
|
||||
await validateOrThrow(disableSchema, data);
|
||||
|
||||
return ApiRequest.post<MetaWapCredentialView>("meta-credentials/disable", data);
|
||||
};
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import MaterialAlert from "@mui/material/Alert";
|
||||
import dayjs from "dayjs";
|
||||
import Button from "@core/app/components/Button/Button";
|
||||
import Textbox from "@components/Textbox/Textbox";
|
||||
import { useConfirmStore } from "@core/Store/Confirm.Store";
|
||||
import { useSessionStore } from "@store/Sesion.Store";
|
||||
import { useAlert } from "@store/Alert.Store";
|
||||
import { useEventHandlerStore, EVENT_TYPES } from "@store/EventHandler.Store";
|
||||
import { ApiError } from "@models/Server.Error.model";
|
||||
import {
|
||||
META_WAP_CREDENTIAL_NOT_CONFIGURED_MESSAGE,
|
||||
META_WAP_CREDENTIAL_STATUS,
|
||||
MetaWapCredentialView,
|
||||
} from "@models/MetaWapCredential.model";
|
||||
import {
|
||||
metaWapCredentialDisable,
|
||||
metaWapCredentialFind,
|
||||
metaWapCredentialSave,
|
||||
metaWapCredentialTest,
|
||||
} from "./MetaWapCredentials.Service";
|
||||
|
||||
export default function MetaWapCredentials({ companyId }: { companyId: string }) {
|
||||
const SessionInfo = useSessionStore();
|
||||
const eventHandler = useEventHandlerStore();
|
||||
const alert = useAlert();
|
||||
const confirm = useConfirmStore();
|
||||
|
||||
const [phoneNumberId, setPhoneNumberId] = useState<string>("");
|
||||
const [wabaId, setWabaId] = useState<string>("");
|
||||
const [accessToken, setAccessToken] = useState<string>("");
|
||||
const [templateName, setTemplateName] = useState<string>("");
|
||||
const [phoneNumber, setPhoneNumber] = useState<string>("");
|
||||
const [languageCode, setLanguageCode] = useState<string>("es");
|
||||
const [templateVariables, setTemplateVariables] = useState<string>("");
|
||||
const [credential, setCredential] = useState<MetaWapCredentialView | undefined>(undefined);
|
||||
|
||||
const formatError = (error: unknown): string => {
|
||||
if (error instanceof ApiError) {
|
||||
return error.format();
|
||||
}
|
||||
return String(error);
|
||||
};
|
||||
|
||||
const isNotConfiguredError = (error: unknown): boolean => {
|
||||
return (
|
||||
error instanceof ApiError &&
|
||||
error.desc.includes(META_WAP_CREDENTIAL_NOT_CONFIGURED_MESSAGE)
|
||||
);
|
||||
};
|
||||
|
||||
const statusLabel = (status: META_WAP_CREDENTIAL_STATUS): string => {
|
||||
switch (status) {
|
||||
case META_WAP_CREDENTIAL_STATUS.CONFIGURED:
|
||||
return "Configuradas";
|
||||
case META_WAP_CREDENTIAL_STATUS.TESTED:
|
||||
return "Probadas";
|
||||
case META_WAP_CREDENTIAL_STATUS.INVALID:
|
||||
return "Inválidas";
|
||||
case META_WAP_CREDENTIAL_STATUS.DISABLED:
|
||||
return "Desactivadas";
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
const statusColor = (status: META_WAP_CREDENTIAL_STATUS): string => {
|
||||
if (status === META_WAP_CREDENTIAL_STATUS.TESTED) {
|
||||
return "var(--green-darkestX2)";
|
||||
}
|
||||
if (
|
||||
status === META_WAP_CREDENTIAL_STATUS.INVALID ||
|
||||
status === META_WAP_CREDENTIAL_STATUS.DISABLED
|
||||
) {
|
||||
return "var(--error)";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const refreshCredential = (): void => {
|
||||
if (!SessionInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
metaWapCredentialFind({
|
||||
companyId: companyId,
|
||||
sessionUser: SessionInfo.userId,
|
||||
})
|
||||
.then((view: MetaWapCredentialView) => {
|
||||
setCredential(view);
|
||||
setPhoneNumberId(view.phoneNumberId ?? "");
|
||||
setWabaId(view.wabaId ?? "");
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (isNotConfiguredError(error)) {
|
||||
setCredential(undefined);
|
||||
return;
|
||||
}
|
||||
alert.showError(formatError(error));
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!SessionInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventHandler.setEventType(EVENT_TYPES.LOADING);
|
||||
metaWapCredentialFind({
|
||||
companyId: companyId,
|
||||
sessionUser: SessionInfo.userId,
|
||||
})
|
||||
.then((view: MetaWapCredentialView) => {
|
||||
setCredential(view);
|
||||
setPhoneNumberId(view.phoneNumberId ?? "");
|
||||
setWabaId(view.wabaId ?? "");
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (isNotConfiguredError(error)) {
|
||||
setCredential(undefined);
|
||||
return;
|
||||
}
|
||||
alert.showError(formatError(error));
|
||||
})
|
||||
.finally(() => {
|
||||
eventHandler.setEventType(EVENT_TYPES.SLEEP);
|
||||
});
|
||||
}, [SessionInfo.userId]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (!SessionInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventHandler.setEventType(EVENT_TYPES.LOADING);
|
||||
metaWapCredentialSave({
|
||||
companyId: companyId,
|
||||
phoneNumberId: phoneNumberId,
|
||||
wabaId: wabaId,
|
||||
accessToken: accessToken,
|
||||
sessionUser: SessionInfo.userId,
|
||||
})
|
||||
.then(() => {
|
||||
setAccessToken("");
|
||||
alert.showSuccess("Credenciales guardadas correctamente");
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
alert.showError(formatError(error));
|
||||
})
|
||||
.finally(() => {
|
||||
refreshCredential();
|
||||
eventHandler.setEventType(EVENT_TYPES.SLEEP);
|
||||
});
|
||||
};
|
||||
|
||||
const handleTest = () => {
|
||||
if (!SessionInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!credential || credential.status === META_WAP_CREDENTIAL_STATUS.DISABLED) {
|
||||
alert.showWarning(
|
||||
"Primero debe configurar las credenciales de Meta para poder enviar un mensaje de prueba."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
eventHandler.setEventType(EVENT_TYPES.LOADING);
|
||||
metaWapCredentialTest({
|
||||
companyId: companyId,
|
||||
phoneNumber: phoneNumber,
|
||||
templateName: templateName,
|
||||
languageCode: languageCode,
|
||||
templateVariables: templateVariables
|
||||
? templateVariables
|
||||
.split(",")
|
||||
.map((variable) => variable.trim())
|
||||
.filter(Boolean)
|
||||
: undefined,
|
||||
sessionUser: SessionInfo.userId,
|
||||
})
|
||||
.then(() => {
|
||||
alert.showSuccess("Mensaje de prueba enviado correctamente");
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
alert.showError(formatError(error));
|
||||
})
|
||||
.finally(() => {
|
||||
refreshCredential();
|
||||
eventHandler.setEventType(EVENT_TYPES.SLEEP);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDisable = () => {
|
||||
confirm.show(
|
||||
"¿Está seguro de desactivar las credenciales de Meta de esta organización?",
|
||||
() => {
|
||||
if (!SessionInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventHandler.setEventType(EVENT_TYPES.LOADING);
|
||||
metaWapCredentialDisable({
|
||||
companyId: companyId,
|
||||
sessionUser: SessionInfo.userId,
|
||||
})
|
||||
.then(() => {
|
||||
alert.showSuccess("Credenciales desactivadas correctamente");
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
alert.showError(formatError(error));
|
||||
})
|
||||
.finally(() => {
|
||||
refreshCredential();
|
||||
eventHandler.setEventType(EVENT_TYPES.SLEEP);
|
||||
});
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<section style={{ marginTop: "20px", padding: "0 16px" }}>
|
||||
<MaterialAlert severity="info" sx={{ marginBottom: "20px" }}>
|
||||
Configure las credenciales de WhatsApp Business (Meta) para poder enviar
|
||||
notificaciones mediante este método.
|
||||
</MaterialAlert>
|
||||
|
||||
<Textbox
|
||||
placeholder="Phone Number ID"
|
||||
type="text"
|
||||
value={phoneNumberId}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setPhoneNumberId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Textbox
|
||||
placeholder="WABA ID"
|
||||
type="text"
|
||||
value={wabaId}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setWabaId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Textbox
|
||||
placeholder="Access Token"
|
||||
type="password"
|
||||
value={accessToken}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setAccessToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Guardar credenciales"
|
||||
width="100%"
|
||||
color="primary"
|
||||
style={{ height: "42px", marginTop: "10px" }}
|
||||
onClick={handleSave}
|
||||
/>
|
||||
|
||||
{credential && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
padding: "12px 16px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid var(--border-color)",
|
||||
backgroundColor: "var(--bg-primary)",
|
||||
}}
|
||||
>
|
||||
<b style={{ color: statusColor(credential.status) }}>
|
||||
Estado: {statusLabel(credential.status)}
|
||||
</b>
|
||||
<div>Phone Number ID: {credential.phoneNumberId}</div>
|
||||
<div>WABA ID: {credential.wabaId}</div>
|
||||
{credential.lastTestedAt && (
|
||||
<div>
|
||||
Última prueba:{" "}
|
||||
{dayjs(credential.lastTestedAt).format("DD/MM/YYYY HH:mm")}
|
||||
</div>
|
||||
)}
|
||||
{credential.lastTestError && (
|
||||
<div style={{ color: "var(--error)" }}>
|
||||
Último error: {credential.lastTestError}
|
||||
</div>
|
||||
)}
|
||||
{credential.status === META_WAP_CREDENTIAL_STATUS.DISABLED && (
|
||||
<MaterialAlert severity="warning" sx={{ marginTop: "10px" }}>
|
||||
Las credenciales están desactivadas. Guarde nuevamente para
|
||||
reactivarlas.
|
||||
</MaterialAlert>
|
||||
)}
|
||||
{credential.status !== META_WAP_CREDENTIAL_STATUS.DISABLED && (
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<Button
|
||||
text="Desactivar credenciales"
|
||||
width="100%"
|
||||
color="error"
|
||||
style={{ height: "42px", marginTop: "10px" }}
|
||||
onClick={handleDisable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: "30px" }}>
|
||||
<MaterialAlert severity="info" sx={{ marginBottom: "20px" }}>
|
||||
Envía un mensaje de prueba con las credenciales guardadas.
|
||||
</MaterialAlert>
|
||||
<Textbox
|
||||
placeholder="Número de destino (con código de país, ej 54911...)"
|
||||
type="text"
|
||||
value={phoneNumber}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setPhoneNumber(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Textbox
|
||||
placeholder="Template (ej: hello_world)"
|
||||
type="text"
|
||||
value={templateName}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setTemplateName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Textbox
|
||||
placeholder="Código de idioma (ej: es)"
|
||||
type="text"
|
||||
value={languageCode}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setLanguageCode(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Textbox
|
||||
placeholder="Variables separadas por coma (opcional)"
|
||||
type="text"
|
||||
value={templateVariables}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setTemplateVariables(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text="Enviar mensaje de prueba"
|
||||
width="100%"
|
||||
color="success"
|
||||
style={{ height: "42px", marginTop: "10px" }}
|
||||
onClick={handleTest}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,11 @@ import StorefrontOutlinedIcon from "@mui/icons-material/StorefrontOutlined";
|
||||
import OrgConfigIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import MaterialAlert from "@mui/material/Alert";
|
||||
import Button from "@core/app/components/Button/Button";
|
||||
import { ThemeProvider } from "@emotion/react";
|
||||
import turnosXpressTheme from "@core/app/theme/turnosXpress";
|
||||
import { ButtonGroup } from "@mui/material";
|
||||
import ButtonMaterial from "@mui/material/Button";
|
||||
import MetaWapCredentials from "./MetaWapCredentials";
|
||||
import {
|
||||
wapServerAssign,
|
||||
wapServerBotCreate,
|
||||
@@ -42,6 +47,7 @@ export default function OrganizationEdit() {
|
||||
|
||||
const [wapView, setWapView] = useState<BotView | undefined>(undefined);
|
||||
const [qr, setQr] = useState<string | undefined>(undefined);
|
||||
const [activeTab, setActiveTab] = useState<"wap" | "meta">("wap");
|
||||
|
||||
useEffect(() => {
|
||||
location.clear();
|
||||
@@ -501,49 +507,78 @@ export default function OrganizationEdit() {
|
||||
<>
|
||||
<Location />
|
||||
|
||||
<SecureByPlan content={SECURE_CONTENTS.WAP} companyId={id}>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "start",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<BotContent />
|
||||
</div>
|
||||
|
||||
{wapView && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
checked={wapView.automaticNotifications}
|
||||
onChange={(checked) => {
|
||||
saveAutomaticNotifications(checked);
|
||||
<div style={{ display: "flex", justifyContent: "center", marginTop: "15px" }}>
|
||||
<ThemeProvider theme={turnosXpressTheme}>
|
||||
<ButtonGroup variant="outlined" aria-label="Basic button group">
|
||||
<ButtonMaterial
|
||||
variant={activeTab === "wap" ? "contained" : "outlined"}
|
||||
onClick={() => {
|
||||
setActiveTab("wap");
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
wapView.automaticNotifications
|
||||
? { color: "var(--green-darkestX2)" }
|
||||
: { color: "var(--error)" }
|
||||
}
|
||||
>
|
||||
{wapView.automaticNotifications
|
||||
? "Notificaciones automaticas activadas"
|
||||
: "Notificaciones automaticas desactivadas"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
WAP
|
||||
</ButtonMaterial>
|
||||
<ButtonMaterial
|
||||
variant={activeTab === "meta" ? "contained" : "outlined"}
|
||||
onClick={() => {
|
||||
setActiveTab("meta");
|
||||
}}
|
||||
>
|
||||
META
|
||||
</ButtonMaterial>
|
||||
</ButtonGroup>
|
||||
</ThemeProvider>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "40px" }}> </div>
|
||||
<SecureByPlan content={SECURE_CONTENTS.WAP} companyId={id}>
|
||||
{activeTab === "wap" ? (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "start",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<BotContent />
|
||||
</div>
|
||||
|
||||
{wapView && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
checked={wapView.automaticNotifications}
|
||||
onChange={(checked) => {
|
||||
saveAutomaticNotifications(checked);
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
style={
|
||||
wapView.automaticNotifications
|
||||
? { color: "var(--green-darkestX2)" }
|
||||
: { color: "var(--error)" }
|
||||
}
|
||||
>
|
||||
{wapView.automaticNotifications
|
||||
? "Notificaciones automaticas activadas"
|
||||
: "Notificaciones automaticas desactivadas"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: "40px" }}> </div>
|
||||
</>
|
||||
) : (
|
||||
<MetaWapCredentials companyId={id} />
|
||||
)}
|
||||
</SecureByPlan>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user