list twilio phone numbers
This commit is contained in:
@ -20,6 +20,7 @@ invariant(
|
||||
`Please define the "AWS_SES_FROM_EMAIL" environment variable`,
|
||||
);
|
||||
invariant(typeof process.env.REDIS_URL === "string", `Please define the "REDIS_URL" environment variable`);
|
||||
invariant(typeof process.env.TWILIO_AUTH_TOKEN === "string", `Please define the "TWILIO_AUTH_TOKEN" environment variable`);
|
||||
|
||||
export default {
|
||||
app: {
|
||||
@ -37,4 +38,7 @@ export default {
|
||||
url: process.env.REDIS_URL,
|
||||
password: process.env.REDIS_PASSWORD,
|
||||
},
|
||||
twilio: {
|
||||
authToken: process.env.TWILIO_AUTH_TOKEN,
|
||||
},
|
||||
};
|
||||
|
@ -1,20 +1,23 @@
|
||||
import { useActionData, useCatch, useTransition } from "@remix-run/react";
|
||||
import { useActionData, useCatch, useLoaderData, useTransition } from "@remix-run/react";
|
||||
|
||||
import Button from "../button";
|
||||
import SettingsSection from "../settings-section";
|
||||
import Alert from "~/features/core/components/alert";
|
||||
import useSession from "~/features/core/hooks/use-session";
|
||||
import { PhoneSettingsLoaderData } from "~/routes/__app/settings/phone";
|
||||
|
||||
export default function PhoneNumberForm() {
|
||||
const transition = useTransition();
|
||||
const actionData = useActionData();
|
||||
const { currentOrganization } = useSession();
|
||||
|
||||
const isSubmitting = transition.state === "submitting";
|
||||
const isSuccess = actionData?.submitted === true;
|
||||
const error = actionData?.error;
|
||||
const isError = !!error;
|
||||
|
||||
const hasFilledTwilioCredentials = false; // Boolean(currentOrganization?.twilioAccountSid && currentOrganization?.twilioAuthToken)
|
||||
const availablePhoneNumbers: any[] = [];
|
||||
const hasFilledTwilioCredentials = Boolean(currentOrganization.twilioAccountSid)
|
||||
const availablePhoneNumbers = useLoaderData<PhoneSettingsLoaderData>().phoneNumbers;
|
||||
|
||||
const onSubmit = async () => {
|
||||
// await setPhoneNumberMutation({ phoneNumberSid }); // TODO
|
||||
|
@ -1,5 +1,30 @@
|
||||
import { type LoaderFunction, json } from "@remix-run/node";
|
||||
|
||||
import TwilioConnect from "~/features/settings/components/phone/twilio-connect";
|
||||
import PhoneNumberForm from "~/features/settings/components/phone/phone-number-form";
|
||||
import { requireLoggedIn } from "~/utils/auth.server";
|
||||
import getTwilioClient from "~/utils/twilio.server";
|
||||
|
||||
export type PhoneSettingsLoaderData = {
|
||||
phoneNumbers: {
|
||||
phoneNumber: string;
|
||||
sid: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const { organizations } = await requireLoggedIn(request);
|
||||
const organization = organizations[0];
|
||||
if (!organization.twilioAccountSid) {
|
||||
throw new Error("Twilio account is not connected");
|
||||
}
|
||||
|
||||
const twilioClient = getTwilioClient(organization);
|
||||
const incomingPhoneNumbers = await twilioClient.incomingPhoneNumbers.list();
|
||||
const phoneNumbers = incomingPhoneNumbers.map(({ phoneNumber, sid }) => ({ phoneNumber, sid }));
|
||||
|
||||
return json<PhoneSettingsLoaderData>({ phoneNumbers });
|
||||
};
|
||||
|
||||
function PhoneSettings() {
|
||||
return (
|
||||
|
@ -2,18 +2,23 @@ import { type LoaderFunction, redirect } from "@remix-run/node";
|
||||
import { refreshSessionData, requireLoggedIn } from "~/utils/auth.server";
|
||||
import db from "~/utils/db.server";
|
||||
import { commitSession } from "~/utils/session.server";
|
||||
import twilio from "twilio";
|
||||
import serverConfig from "~/config/config.server";
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const user = await requireLoggedIn(request);
|
||||
const url = new URL(request.url);
|
||||
const twilioAccountSid = url.searchParams.get("AccountSid");
|
||||
if (!twilioAccountSid) {
|
||||
const twilioSubAccountSid = url.searchParams.get("AccountSid");
|
||||
if (!twilioSubAccountSid) {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
|
||||
const twilioClient = twilio(twilioSubAccountSid, serverConfig.twilio.authToken);
|
||||
const twilioSubAccount = await twilioClient.api.accounts(twilioSubAccountSid).fetch();
|
||||
const twilioAccountSid = twilioSubAccount.ownerAccountSid;
|
||||
await db.organization.update({
|
||||
where: { id: user.organizations[0].id },
|
||||
data: { twilioAccountSid },
|
||||
data: { twilioSubAccountSid, twilioAccountSid },
|
||||
});
|
||||
|
||||
const { session } = await refreshSessionData(request);
|
||||
|
@ -9,7 +9,7 @@ import authenticator from "./authenticator.server";
|
||||
import { AuthenticationError } from "./errors";
|
||||
import { commitSession, destroySession, getSession } from "./session.server";
|
||||
|
||||
export type SessionOrganization = Pick<Organization, "id" | "twilioAccountSid"> & { role: MembershipRole };
|
||||
export type SessionOrganization = Pick<Organization, "id" | "twilioSubAccountSid" | "twilioAccountSid"> & { role: MembershipRole };
|
||||
export type SessionUser = Omit<User, "hashedPassword"> & {
|
||||
organizations: SessionOrganization[];
|
||||
};
|
||||
@ -39,7 +39,7 @@ export async function login({ form }: FormStrategyVerifyParams): Promise<Session
|
||||
memberships: {
|
||||
select: {
|
||||
organization: {
|
||||
select: { id: true, twilioAccountSid: true },
|
||||
select: { id: true, twilioSubAccountSid: true, twilioAccountSid: true },
|
||||
},
|
||||
role: true,
|
||||
},
|
||||
@ -151,7 +151,7 @@ export async function refreshSessionData(request: Request) {
|
||||
memberships: {
|
||||
select: {
|
||||
organization: {
|
||||
select: { id: true, twilioAccountSid: true },
|
||||
select: { id: true, twilioSubAccountSid: true, twilioAccountSid: true },
|
||||
},
|
||||
role: true,
|
||||
},
|
||||
|
16
app/utils/twilio.server.ts
Normal file
16
app/utils/twilio.server.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import twilio from "twilio";
|
||||
|
||||
import type { Organization } from "@prisma/client";
|
||||
import serverConfig from "~/config/config.server";
|
||||
|
||||
type MinimalOrganization = Pick<Organization, "twilioSubAccountSid" | "twilioAccountSid">;
|
||||
|
||||
export default function getTwilioClient(organization: MinimalOrganization): twilio.Twilio {
|
||||
if (!organization || !organization.twilioSubAccountSid || !organization.twilioAccountSid) {
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
|
||||
return twilio(organization.twilioSubAccountSid, serverConfig.twilio.authToken, {
|
||||
accountSid: organization.twilioAccountSid,
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user