195 lines
6.5 KiB
TypeScript
195 lines
6.5 KiB
TypeScript
import { auth, Client, webln } from "@getalby/sdk";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { useState, useCallback, useEffect } from "react";
|
|
import { Toaster } from "react-hot-toast";
|
|
|
|
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { trpc } from "@calcom/trpc";
|
|
import { Badge, Button, showToast } from "@calcom/ui";
|
|
import { Icon } from "@calcom/ui";
|
|
|
|
import { albyCredentialKeysSchema } from "../../lib/albyCredentialKeysSchema";
|
|
|
|
export interface IAlbySetupProps {
|
|
email: string | null;
|
|
lightningAddress: string | null;
|
|
clientId: string;
|
|
clientSecret: string;
|
|
}
|
|
|
|
export default function AlbySetup(props: IAlbySetupProps) {
|
|
const params = useCompatSearchParams();
|
|
if (params?.get("callback") === "true") {
|
|
return <AlbySetupCallback />;
|
|
}
|
|
|
|
return <AlbySetupPage {...props} />;
|
|
}
|
|
|
|
function AlbySetupCallback() {
|
|
const [error, setError] = useState<string | null>(null);
|
|
const searchParams = useCompatSearchParams();
|
|
|
|
useEffect(() => {
|
|
if (!searchParams) {
|
|
return;
|
|
}
|
|
|
|
if (!window.opener) {
|
|
setError("Something went wrong. Opener not available. Please contact support@getalby.com");
|
|
return;
|
|
}
|
|
|
|
const code = searchParams?.get("code");
|
|
const error = searchParams?.get("error");
|
|
|
|
if (!code) {
|
|
setError("declined");
|
|
}
|
|
if (error) {
|
|
setError(error);
|
|
alert(error);
|
|
return;
|
|
}
|
|
|
|
window.opener.postMessage({
|
|
type: "alby:oauth:success",
|
|
payload: { code },
|
|
});
|
|
window.close();
|
|
}, [searchParams]);
|
|
|
|
return (
|
|
<div>
|
|
{error && <p>Authorization failed: {error}</p>}
|
|
{!error && <p>Connecting...</p>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AlbySetupPage(props: IAlbySetupProps) {
|
|
const router = useRouter();
|
|
const { t } = useLocale();
|
|
const integrations = trpc.viewer.integrations.useQuery({ variant: "payment", appId: "alby" });
|
|
const [albyPaymentAppCredentials] = integrations.data?.items || [];
|
|
const [credentialId] = albyPaymentAppCredentials?.userCredentialIds || [-1];
|
|
const showContent = !!integrations.data && integrations.isSuccess && !!credentialId;
|
|
const saveKeysMutation = trpc.viewer.appsRouter.updateAppCredentials.useMutation({
|
|
onSuccess: () => {
|
|
showToast(t("keys_have_been_saved"), "success");
|
|
router.push("/event-types");
|
|
},
|
|
onError: (error) => {
|
|
showToast(error.message, "error");
|
|
},
|
|
});
|
|
|
|
const connectWithAlby = useCallback(async () => {
|
|
const authClient = new auth.OAuth2User({
|
|
client_id: props.clientId,
|
|
client_secret: props.clientSecret,
|
|
callback: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/apps/alby/setup?callback=true`,
|
|
scopes: ["invoices:read", "account:read"],
|
|
user_agent: "cal.com",
|
|
});
|
|
|
|
const weblnOAuthProvider = new webln.OauthWeblnProvider({
|
|
auth: authClient,
|
|
});
|
|
await weblnOAuthProvider.enable();
|
|
|
|
const client = new Client(authClient);
|
|
const accountInfo = await client.accountInformation({});
|
|
// TODO: add a way to delete the endpoint when the app is uninstalled
|
|
const webhookEndpoint = await client.createWebhookEndpoint({
|
|
filter_types: ["invoice.incoming.settled"],
|
|
url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/integrations/alby/webhook`,
|
|
description: "Cal.com",
|
|
});
|
|
|
|
saveKeysMutation.mutate({
|
|
credentialId,
|
|
key: albyCredentialKeysSchema.parse({
|
|
account_id: accountInfo.identifier,
|
|
account_email: accountInfo.email,
|
|
account_lightning_address: accountInfo.lightning_address,
|
|
webhook_endpoint_id: webhookEndpoint.id,
|
|
webhook_endpoint_secret: webhookEndpoint.endpoint_secret,
|
|
}),
|
|
});
|
|
}, [credentialId, props.clientId, props.clientSecret, saveKeysMutation]);
|
|
|
|
if (integrations.isPending) {
|
|
return <div className="absolute z-50 flex h-screen w-full items-center bg-gray-200" />;
|
|
}
|
|
|
|
const albyIcon = (
|
|
<>
|
|
<img className="h-12 w-12 dark:hidden" src="/api/app-store/alby/icon-borderless.svg" alt="Alby Icon" />
|
|
<img
|
|
className="hidden h-12 w-12 dark:block"
|
|
src="/api/app-store/alby/icon-borderless-dark.svg"
|
|
alt="Alby Icon"
|
|
/>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div className="bg-default flex h-screen">
|
|
{showContent ? (
|
|
<div className="flex w-full items-center justify-center p-4">
|
|
<div className="bg-default border-subtle m-auto flex max-w-[43em] flex-col items-center justify-center gap-4 overflow-auto rounded border p-4 md:p-10">
|
|
{!props.lightningAddress ? (
|
|
<>
|
|
<p className="text-default">
|
|
Create or connect to an existing Alby account to receive lightning payments for your paid
|
|
bookings.
|
|
</p>
|
|
<button
|
|
className="font-body flex h-10 w-56 items-center justify-center gap-2 rounded-md font-bold text-black shadow transition-all hover:brightness-90 active:scale-95"
|
|
style={{
|
|
background: "linear-gradient(180deg, #FFDE6E 63.72%, #F8C455 95.24%)",
|
|
}}
|
|
type="button"
|
|
onClick={connectWithAlby}>
|
|
{albyIcon}
|
|
<span className="mr-2">Connect with Alby</span>
|
|
</button>
|
|
</>
|
|
) : (
|
|
<>
|
|
{albyIcon}
|
|
<p>Alby Connected!</p>
|
|
<Badge>Email: {props.email}</Badge>
|
|
<Badge>Lightning Address: {props.lightningAddress}</Badge>
|
|
</>
|
|
)}
|
|
|
|
{/* TODO: remove when invoices are generated using user identifier */}
|
|
<div className="mt-4 rounded bg-blue-50 p-3 text-sm text-blue-700 dark:bg-blue-950 dark:text-blue-300">
|
|
<Icon name="info" className="mb-0.5 inline-flex h-4 w-4" /> Your Alby lightning address will be
|
|
used to generate invoices. If you update your lightning address, please disconnect and setup the
|
|
Alby app again.
|
|
</div>
|
|
<Link href="/apps/alby">
|
|
<Button color="secondary">Go to App Store Listing</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="ml-5 mt-5">
|
|
<div>Alby</div>
|
|
<div className="mt-3">
|
|
<Link href="/apps/alby" passHref={true} legacyBehavior>
|
|
<Button>{t("go_to_app_store")}</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<Toaster position="bottom-right" />
|
|
</div>
|
|
);
|
|
}
|