import { useMemo, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { AnimatePresence, motion } from 'framer-motion';
import { Building2Icon, PlusIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { trpc } from '@documenso/trpc/react';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { RadioGroup, RadioGroupItem } from '@documenso/ui/primitives/radio-group';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { ZCreateOrganisationFormSchema } from '../dialogs/organisation-create-dialog';
const MotionCard = motion(Card);
export type BillingPlansProps = {
plans: InternalClaimPlans;
};
export const BillingPlans = ({ plans }: BillingPlansProps) => {
const isMounted = useIsMounted();
const { organisations } = useSession();
const [interval, setInterval] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice');
const isPersonalLayoutMode = isPersonalLayout(organisations);
const pricesToDisplay = useMemo(() => {
const prices = [];
for (const plan of Object.values(plans)) {
if (plan[interval] && plan[interval].isVisibleInApp) {
prices.push({
...plan[interval],
memberCount: plan.memberCount,
claim: plan.id,
});
}
}
return prices;
}, [plans, interval]);
return (
setInterval(value as 'monthlyPrice' | 'yearlyPrice')}
>
Monthly
Yearly
{pricesToDisplay.map((price) => (
{price.product.name}
{price.friendlyPrice + ' '}
{interval === 'monthlyPrice' ? (
per month
) : (
per year
)}
{price.product.description}
{price.product.features && price.product.features.length > 0 && (
Includes:
{price.product.features.map((feature, index) => (
-
{feature.name}
))}
)}
{isPersonalLayoutMode && price.claim === INTERNAL_CLAIM_ID.INDIVIDUAL ? (
Subscribe
) : (
)}
))}
);
};
const BillingDialog = ({
priceId,
planName,
claim,
}: {
priceId: string;
planName: string;
memberCount: number;
claim: string;
}) => {
const [isOpen, setIsOpen] = useState(false);
const { t } = useLingui();
const { toast } = useToast();
const organisation = useCurrentOrganisation();
const [subscriptionOption, setSubscriptionOption] = useState<'update' | 'create'>(
organisation.type === 'PERSONAL' && claim === INTERNAL_CLAIM_ID.INDIVIDUAL
? 'update'
: 'create',
);
const [step, setStep] = useState(0);
const form = useForm({
resolver: zodResolver(ZCreateOrganisationFormSchema),
defaultValues: {
name: '',
},
});
const { mutateAsync: createSubscription, isPending: isCreatingSubscription } =
trpc.enterprise.billing.subscription.create.useMutation();
const { mutateAsync: createOrganisation, isPending: isCreatingOrganisation } =
trpc.organisation.create.useMutation();
const isPending = isCreatingSubscription || isCreatingOrganisation;
const onSubscribeClick = async () => {
try {
let redirectUrl = '';
if (subscriptionOption === 'update') {
const createSubscriptionResponse = await createSubscription({
organisationId: organisation.id,
priceId,
});
redirectUrl = createSubscriptionResponse.redirectUrl;
} else {
const createOrganisationResponse = await createOrganisation({
name: form.getValues('name'),
priceId,
});
if (!createOrganisationResponse.paymentRequired) {
setIsOpen(false);
return;
}
redirectUrl = createOrganisationResponse.checkoutUrl;
}
window.location.href = redirectUrl;
} catch (_err) {
toast({
title: t`Something went wrong`,
description: t`An error occurred while trying to create a checkout session.`,
variant: 'destructive',
});
}
};
return (
);
};
/**
* Custom checkout button for individual organisations in personal layout mode.
*
* This is so they don't create an additional organisation which is not needed since
* it will clutter up the UI for them with unnecessary organisations.
*/
export const IndividualPersonalLayoutCheckoutButton = ({
priceId,
children,
}: {
priceId: string;
children: React.ReactNode;
}) => {
const { t } = useLingui();
const { toast } = useToast();
const { organisations } = useSession();
const { mutateAsync: createSubscription, isPending } =
trpc.enterprise.billing.subscription.create.useMutation();
const onSubscribeClick = async () => {
try {
const createSubscriptionResponse = await createSubscription({
organisationId: organisations[0].id,
priceId,
isPersonalLayoutMode: true,
});
window.location.href = createSubscriptionResponse.redirectUrl;
} catch (_err) {
toast({
title: t`Something went wrong`,
description: t`An error occurred while trying to create a checkout session.`,
variant: 'destructive',
});
}
};
return (
);
};