Move metamigo assets to metamigo-add
This commit is contained in:
parent
aab5b7f5d5
commit
28f7f0f47b
71 changed files with 3 additions and 2 deletions
475
apps/link/metamigo-add/_components/signal/bots/SignalBotShow.tsx
Normal file
475
apps/link/metamigo-add/_components/signal/bots/SignalBotShow.tsx
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import {
|
||||
Show,
|
||||
SimpleShowLayout,
|
||||
BooleanField,
|
||||
TextField,
|
||||
ShowProps,
|
||||
EditButton,
|
||||
TopToolbar,
|
||||
useTranslate,
|
||||
useRefresh,
|
||||
} from "react-admin";
|
||||
import {
|
||||
TextField as MuiTextField,
|
||||
Button,
|
||||
Card,
|
||||
Grid,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Typography,
|
||||
Box,
|
||||
CircularProgress,
|
||||
} from "@mui/material";
|
||||
import { SixDigitInput } from "../../DigitInput";
|
||||
import {
|
||||
sanitizeE164Number,
|
||||
isValidE164Number,
|
||||
} from "../../../_lib/phone-numbers";
|
||||
|
||||
const Sidebar = ({ record }: any) => {
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [errorNumber, setErrorNumber] = useState(false);
|
||||
const handlePhoneNumberChange = (event: any) => {
|
||||
setPhoneNumber(event.target.value);
|
||||
};
|
||||
|
||||
const [message, setMessage] = useState("");
|
||||
const handleMessageChange = (event: any) => {
|
||||
setMessage(event.target.value);
|
||||
};
|
||||
|
||||
const sendMessage = async (phoneNumber: string, message: string) => {
|
||||
await fetch(`/api/v1/signal/bots/${record.token}/send`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ phoneNumber, message }),
|
||||
});
|
||||
};
|
||||
|
||||
const resetSession = async (phoneNumber: string) => {
|
||||
await fetch(`/api/v1/signal/bots/${record.token}/resetSession`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ phoneNumber }),
|
||||
});
|
||||
};
|
||||
|
||||
const handleBlurNumber = () => {
|
||||
setErrorNumber(!isValidE164Number(sanitizeE164Number(phoneNumber)));
|
||||
};
|
||||
|
||||
const handleSend = () => {
|
||||
const sanitized = sanitizeE164Number(phoneNumber);
|
||||
if (isValidE164Number(sanitized)) {
|
||||
setErrorNumber(false);
|
||||
sendMessage(sanitized, message);
|
||||
} else setErrorNumber(false);
|
||||
};
|
||||
|
||||
const handleResetSession = () => {
|
||||
const sanitized = sanitizeE164Number(phoneNumber);
|
||||
if (isValidE164Number(sanitized)) {
|
||||
setErrorNumber(false);
|
||||
resetSession(sanitized);
|
||||
} else setErrorNumber(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={{ width: "33%", marginLeft: 20, padding: 14 }}>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
<Typography variant="h6">Send message</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<MuiTextField
|
||||
variant="outlined"
|
||||
label="Phone number"
|
||||
fullWidth
|
||||
size="small"
|
||||
error={errorNumber}
|
||||
onBlur={handleBlurNumber}
|
||||
value={phoneNumber}
|
||||
onChange={handlePhoneNumberChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<MuiTextField
|
||||
variant="outlined"
|
||||
label="Message"
|
||||
multiline
|
||||
minRows={3}
|
||||
fullWidth
|
||||
size="small"
|
||||
value={message}
|
||||
onChange={handleMessageChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item container direction="row-reverse">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => handleSend()}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
<Button variant="contained" onClick={() => handleResetSession()}>
|
||||
Reset Session
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const MODE = {
|
||||
SMS: "SMS",
|
||||
VOICE: "VOICE",
|
||||
};
|
||||
|
||||
const handleRequestCode = async ({
|
||||
verifyMode,
|
||||
id,
|
||||
onSuccess,
|
||||
onError,
|
||||
captchaCode = undefined,
|
||||
}: any) => {
|
||||
if (verifyMode === MODE.SMS) console.log("REQUESTING sms");
|
||||
else if (verifyMode === MODE.VOICE) console.log("REQUESTING voice");
|
||||
let response: Response;
|
||||
let url = `/api/v1/signal/bots/${id}/requestCode?mode=${verifyMode.toLowerCase()}`;
|
||||
if (captchaCode) {
|
||||
url += `&captcha=${captchaCode}`;
|
||||
}
|
||||
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
onSuccess();
|
||||
} else {
|
||||
onError(response.status || 400);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Failed to request verification code:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const VerificationCodeRequest = ({
|
||||
verifyMode,
|
||||
data,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: any) => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await handleRequestCode({
|
||||
verifyMode,
|
||||
id: data.id,
|
||||
onSuccess,
|
||||
onError,
|
||||
});
|
||||
})();
|
||||
}, [data.id, onError, onSuccess, verifyMode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Requesting code for {data.phoneNumber}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box display="flex">
|
||||
<Box m="auto">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VerificationCaptcha = ({
|
||||
verifyMode,
|
||||
data,
|
||||
onSuccess,
|
||||
onError,
|
||||
handleClose,
|
||||
}: any) => {
|
||||
const [code, setCode] = useState(undefined);
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
|
||||
const handleSubmitVerification = async () => {
|
||||
setSubmitting(true);
|
||||
await handleRequestCode({
|
||||
verifyMode,
|
||||
id: data.id,
|
||||
onSuccess,
|
||||
onError,
|
||||
captchaCode: code,
|
||||
});
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
const handleCaptchaChange = (value: any) => {
|
||||
if (value)
|
||||
setCode(
|
||||
value
|
||||
.replace(/signalcaptcha:\/\//, "")
|
||||
.replace("“", "")
|
||||
.replace("”", "")
|
||||
.trim(),
|
||||
);
|
||||
else setCode(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Captcha for {data.phoneNumber}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<MuiTextField
|
||||
value={code}
|
||||
onChange={(ev) => handleCaptchaChange(ev.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{isSubmitting && <CircularProgress />}
|
||||
{!isSubmitting && (
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{!isSubmitting && (
|
||||
<Button onClick={handleSubmitVerification} color="primary">
|
||||
Request
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VerificationCodeInput = ({
|
||||
data,
|
||||
verifyMode,
|
||||
handleClose,
|
||||
handleRestartVerification,
|
||||
confirmVerification,
|
||||
}: any) => {
|
||||
const [code, setValue] = useState("");
|
||||
const [isSubmitting, setSubmitting] = useState(false);
|
||||
const [isValid, setValid] = useState(false);
|
||||
const [submissionError, setSubmissionError] = useState(undefined);
|
||||
const translate = useTranslate();
|
||||
|
||||
const validator = (v: any) => v.trim().length === 6;
|
||||
|
||||
const handleValueChange = (newValue: any) => {
|
||||
setValue(newValue);
|
||||
setValid(validator(newValue));
|
||||
};
|
||||
|
||||
const handleSubmitVerification = async () => {
|
||||
setSubmitting(true);
|
||||
// await sleep(2000)
|
||||
const response = await fetch(
|
||||
`/api/v1/signal/bots/${data.id}/register?code=${code}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
setSubmitting(false);
|
||||
const responseBody = await response.json();
|
||||
console.log(responseBody);
|
||||
if (response.status === 200) {
|
||||
confirmVerification();
|
||||
} else if (responseBody.message)
|
||||
setSubmissionError(`Error: ${responseBody.message}`);
|
||||
else {
|
||||
setSubmissionError(
|
||||
"There was an error, sorry about that. Please try again later or contact support.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const title =
|
||||
verifyMode === MODE.SMS
|
||||
? translate("resources.signalBots.verifyDialog.sms", {
|
||||
phoneNumber: data.phoneNumber,
|
||||
})
|
||||
: translate("resources.signalBots.verifyDialog.voice", {
|
||||
phoneNumber: data.phoneNumber,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Verify {data.phoneNumber}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>{title}</DialogContentText>
|
||||
<SixDigitInput value={code} onChange={handleValueChange} />
|
||||
{submissionError && (
|
||||
<Typography variant="body1" gutterBottom color="error">
|
||||
{submissionError}
|
||||
</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{isSubmitting && <CircularProgress />}
|
||||
{!isSubmitting && (
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{!isSubmitting && (
|
||||
<Button onClick={handleRestartVerification} color="primary">
|
||||
Restart
|
||||
</Button>
|
||||
)}
|
||||
{!isSubmitting && (
|
||||
<Button
|
||||
onClick={handleSubmitVerification}
|
||||
color="primary"
|
||||
disabled={!isValid}
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VerificationCodeDialog = (props: any) => {
|
||||
const [stage, setStage] = useState("request");
|
||||
const onRequestSuccess = () => setStage("verify");
|
||||
const onRestartVerification = () => setStage("request");
|
||||
const handleClose = () => {
|
||||
setStage("request");
|
||||
props.handleClose();
|
||||
};
|
||||
|
||||
const onError = (code: number) => {
|
||||
if (code === 402 || code === 500) {
|
||||
setStage("captcha");
|
||||
} else {
|
||||
setStage("request");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={props.open}
|
||||
onClose={props.handleClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
{props.open && stage === "request" && (
|
||||
<VerificationCodeRequest
|
||||
mode={props.verifyMode}
|
||||
onSuccess={onRequestSuccess}
|
||||
onError={onError}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
{props.open && stage === "verify" && (
|
||||
<VerificationCodeInput
|
||||
{...props}
|
||||
handleRestartVerification={onRestartVerification}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
{props.open && stage === "captcha" && (
|
||||
<VerificationCaptcha
|
||||
mode={props.verifyMode}
|
||||
onSuccess={onRequestSuccess}
|
||||
onError={onRestartVerification}
|
||||
handleClose={handleClose}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const SignalBotShowActions = ({ data }: any) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [verifyMode, setVerifyMode] = useState("");
|
||||
const refresh = useRefresh();
|
||||
|
||||
const handleOpenSMS = () => {
|
||||
setVerifyMode(MODE.SMS);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenVoice = () => {
|
||||
setVerifyMode(MODE.VOICE);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => setOpen(false);
|
||||
const confirmVerification = () => {
|
||||
setOpen(false);
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<TopToolbar>
|
||||
<EditButton record={data} />
|
||||
{data && !data.isVerified && (
|
||||
<Button onClick={handleOpenSMS} color="primary">
|
||||
Verify with SMS
|
||||
</Button>
|
||||
)}
|
||||
{data && !data.isVerified && (
|
||||
<Button onClick={handleOpenVoice} color="primary">
|
||||
Verify with Voice
|
||||
</Button>
|
||||
)}
|
||||
{data && !data.isVerified && (
|
||||
<VerificationCodeDialog
|
||||
data={data}
|
||||
verifyMode={verifyMode}
|
||||
handleClose={handleClose}
|
||||
open={open}
|
||||
confirmVerification={confirmVerification}
|
||||
/>
|
||||
)}
|
||||
</TopToolbar>
|
||||
);
|
||||
};
|
||||
|
||||
const SignalBotShow: FC<ShowProps> = (props) => (
|
||||
<Show
|
||||
actions={<SignalBotShowActions />}
|
||||
{...props}
|
||||
title="Signal Bot"
|
||||
aside={<Sidebar />}
|
||||
>
|
||||
<SimpleShowLayout>
|
||||
<TextField source="phoneNumber" />
|
||||
<BooleanField source="isVerified" />
|
||||
<TextField source="description" />
|
||||
<TextField source="token" />
|
||||
</SimpleShowLayout>
|
||||
</Show>
|
||||
);
|
||||
|
||||
export default SignalBotShow;
|
||||
Loading…
Add table
Add a link
Reference in a new issue