135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
import type { BlitzPage, ErrorFallbackProps, WithRouterProps } from "blitz";
|
|
import { ErrorBoundary, Routes, useRouter, withRouter } from "blitz";
|
|
import { useCallback, useEffect } from "react";
|
|
import type { TwilioError } from "@twilio/voice-sdk";
|
|
import { atom, useAtom } from "jotai";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faPhoneAlt as faPhone } from "@fortawesome/pro-solid-svg-icons";
|
|
|
|
import useRequireOnboarding from "../../../core/hooks/use-require-onboarding";
|
|
import useMakeCall from "../../hooks/use-make-call";
|
|
import useDevice from "../../hooks/use-device";
|
|
|
|
import Keypad from "../../components/keypad";
|
|
|
|
const OutgoingCall: BlitzPage = () => {
|
|
useRequireOnboarding();
|
|
const [phoneNumber, setPhoneNumber] = useAtom(phoneNumberAtom);
|
|
const router = useRouter();
|
|
const { isDeviceReady } = useDevice();
|
|
const recipient = decodeURIComponent(router.params.recipient);
|
|
const onHangUp = useCallback(() => setPhoneNumber(""), [setPhoneNumber]);
|
|
const call = useMakeCall({ recipient, onHangUp });
|
|
const pressDigit = useAtom(pressDigitAtom)[1];
|
|
const onDigitPressProps = useCallback(
|
|
(digit: string) => ({
|
|
onPress() {
|
|
pressDigit(digit);
|
|
|
|
call.sendDigits(digit);
|
|
},
|
|
}),
|
|
[call, pressDigit],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (isDeviceReady) {
|
|
call.makeCall();
|
|
}
|
|
}, [isDeviceReady]);
|
|
|
|
return (
|
|
<div className="w-96 h-full flex flex-col justify-around py-5 mx-auto text-center text-black bg-white">
|
|
<div className="h-16 text-3xl text-gray-700">
|
|
<span>{recipient}</span>
|
|
</div>
|
|
|
|
<div className="mb-4 text-gray-600">
|
|
<div className="h-8 text-2xl">{phoneNumber}</div>
|
|
<div className="h-8 text-lg">{translateState(call.state)}</div>
|
|
</div>
|
|
|
|
<Keypad onDigitPressProps={onDigitPressProps} onZeroPressProps={onDigitPressProps("0")}>
|
|
<button
|
|
onClick={call.hangUp}
|
|
className="cursor-pointer select-none col-start-2 h-12 w-12 flex justify-center items-center mx-auto bg-red-800 rounded-full"
|
|
>
|
|
<FontAwesomeIcon className="w-6 h-6" icon={faPhone} color="white" size="lg" />
|
|
</button>
|
|
</Keypad>
|
|
</div>
|
|
);
|
|
|
|
function translateState(state: typeof call.state): string {
|
|
switch (state) {
|
|
case "initial":
|
|
case "ready":
|
|
return "Connecting...";
|
|
case "calling":
|
|
return "Calling...";
|
|
case "call_in_progress":
|
|
return "In call"; // TODO display time elapsed
|
|
case "call_ending":
|
|
return "Call ending...";
|
|
case "call_ended":
|
|
return "Call ended";
|
|
}
|
|
}
|
|
};
|
|
|
|
const phoneNumberAtom = atom("");
|
|
const pressDigitAtom = atom(null, (get, set, digit: string) => {
|
|
if (get(phoneNumberAtom).length > 17) {
|
|
return;
|
|
}
|
|
|
|
set(phoneNumberAtom, (prevState) => prevState + digit);
|
|
});
|
|
|
|
OutgoingCall.authenticate = { redirectTo: Routes.SignIn() };
|
|
OutgoingCall.getLayout = (page) => <ErrorBoundary FallbackComponent={ErrorFallback}>{page}</ErrorBoundary>;
|
|
|
|
const ErrorFallback = withRouter(function WrappedErrorFallback({
|
|
error,
|
|
router,
|
|
}: ErrorFallbackProps & WithRouterProps) {
|
|
console.log("error", JSON.parse(JSON.stringify(error)));
|
|
return (
|
|
<div className="w-screen h-screen flex">
|
|
<div className="max-w-md m-auto p-4">
|
|
<h2 className="text-lg py-3 leading-relaxed font-medium text-gray-900">
|
|
Sorry, an error has occurred during your call
|
|
</h2>
|
|
{isTwilioError(error) ? (
|
|
<pre className="break-normal whitespace-normal mt-2 text-sm rounded py-3 px-5 bg-[#111] text-gray-300">
|
|
<div>
|
|
{error.description} ({error.code})
|
|
</div>
|
|
<div>{error.explanation}</div>
|
|
</pre>
|
|
) : null}
|
|
<p className="mt-2 text-sm text-gray-500">
|
|
We have been automatically notified and we're doing our best to make sure this does not happen
|
|
again!
|
|
</p>
|
|
|
|
<div className="mt-5 md:mt-4 md:flex md:flex-row-reverse">
|
|
<button
|
|
type="button"
|
|
className="transition-colors duration-150 mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 md:mt-0 md:w-auto md:text-sm"
|
|
onClick={() => router.replace(Routes.KeypadPage())}
|
|
>
|
|
Take me back to the app
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
function isTwilioError(error: any): error is typeof TwilioError {
|
|
return error.hasOwnProperty("explanation");
|
|
}
|
|
|
|
export default OutgoingCall;
|