plane/apps/app/components/workspace/upgrade-to-pro-modal.tsx
srinivas pendem eba2f3820a
feat: web waitlist modal integration (#1487)
* dev : Updating the limit of the issues in the sidebar and a weight list modal

* dev: integrated supabase and implemented web waitlist api endpoint

* dev : updated web pro weightlist request

* dev: rename typo

* dev: web waitlist endpoint update

* update: ui fixes

* fix: removed supabase from env.example

* chore: replaced supabase npm package to cdn

* chore: updated supabase req

* fix: Handled error status and error message.

---------

Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-07-11 16:27:29 +05:30

249 lines
9.6 KiB
TypeScript

import React, { useState, useEffect } from "react";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// icons
import { XCircleIcon, RocketLaunchIcon } from "@heroicons/react/24/outline";
import { CheckCircleIcon } from "@heroicons/react/24/solid";
// ui
import { CircularProgress } from "components/ui";
// types
import type { ICurrentUserResponse, IWorkspace } from "types";
declare global {
interface Window {
supabase: any;
}
}
type Props = {
isOpen: boolean;
onClose: () => void;
user: ICurrentUserResponse | undefined;
issueNumber: number;
};
const UpgradeToProModal: React.FC<Props> = ({ isOpen, onClose, user, issueNumber }) => {
const [supabaseClient, setSupabaseClient] = useState<any>(null);
useEffect(() => {
// Create a Supabase client
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
const { createClient } = window.supabase;
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
}
);
if (supabase) {
setSupabaseClient(supabase);
}
}
}, []);
const [isLoading, setIsLoading] = useState(false);
const handleClose = () => {
onClose();
setIsLoading(false);
};
const proFeatures = [
"Everything in free",
"Unlimited users",
"Unlimited file uploads",
"Priority Support",
"Custom Theming",
"Access to Roadmap",
"Plane AI (GPT unlimited)",
];
const [errorMessage, setErrorMessage] = useState<null | { status: String; message: string }>(
null
);
const [loader, setLoader] = useState(false);
const submitEmail = async () => {
setLoader(true);
const payload = { email: user?.email || "" };
if (supabaseClient) {
if (payload?.email) {
const emailExists = await supabaseClient
.from("web-waitlist")
.select("id,email,count")
.eq("email", payload?.email);
if (emailExists.data.length === 0) {
const emailCreation = await supabaseClient
.from("web-waitlist")
.insert([{ email: payload?.email, count: 1, last_visited: new Date() }])
.select("id,email,count");
if (emailCreation.status === 201)
setErrorMessage({ status: "success", message: "Successfully registered." });
else setErrorMessage({ status: "insert_error", message: "Insertion Error." });
} else {
const emailCountUpdate = await supabaseClient
.from("web-waitlist")
.upsert({
id: emailExists.data[0]?.id,
count: emailExists.data[0]?.count + 1,
last_visited: new Date(),
})
.select("id,email,count");
if (emailCountUpdate.status === 201)
setErrorMessage({
status: "email_already_exists",
message: "Email already exists.",
});
else setErrorMessage({ status: "update_error", message: "Update Error." });
}
} else setErrorMessage({ status: "email_required", message: "Please provide email." });
} else
setErrorMessage({
status: "supabase_error",
message: "Network error. Please try again later.",
});
setLoader(false);
};
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-100 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-4xl">
<div className="flex flex-wrap">
<div className="w-full md:w-3/5 p-6 flex flex-col gap-y-6">
<div className="flex gap-2">
<div
className={`font-semibold outline-none text-sm mt-1.5 ${
issueNumber >= 750
? "text-red-600"
: issueNumber >= 500
? "text-yellow-600"
: "text-green-600"
}`}
title="Shortcuts"
>
<CircularProgress
progress={
(issueNumber / 1024) * 100 > 100 ? 100 : (issueNumber / 1024) * 100
}
/>
</div>
<div className="">
<div className="font-semibold text-lg">Upgrade to pro</div>
<div className="text-custom-text-200 text-sm">
This workspace has used {issueNumber} of its 1024 issues creation limit (
{((issueNumber / 1024) * 100).toFixed(2)}%).
</div>
</div>
<div
onClick={handleClose}
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 md:hidden block ml-auto"
>
<XCircleIcon />
</div>
</div>
<div className="flex gap-2 pt-6">
<div
className={`font-semibold outline-none text-sm mt-1.5 w-5 h-5 text-[#892FFF] flex-shrink-0`}
title="Shortcuts"
>
<RocketLaunchIcon />
</div>
<div className="">
<div className="font-semibold text-lg">Order summary</div>
<div className="text-custom-text-200 text-sm">
Priority support, file uploads, and access to premium features.
</div>
<div className="flex flex-wrap my-4">
{proFeatures.map((feature, index) => (
<div key={index} className="w-1/2 py-2 flex gap-2 my-1.5">
<div className="w-5 h-5 mt-0.5 text-green-600">
<CheckCircleIcon />
</div>
<div>{feature}</div>
</div>
))}
</div>
</div>
</div>
</div>
<div className="w-full md:w-2/5 bg-custom-background-90 p-6 flex flex-col">
<div className="flex justify-between items-center">
<div className="font-semibold text-lg">Summary</div>
<div
onClick={handleClose}
className="w-5 h-5 text-custom-text-200 cursor-pointer mt-1.5 hidden md:block"
>
<XCircleIcon />
</div>
</div>
<div className="text-custom-text-200 text-sm mt-4">
Plane application is currently in dev-mode. We will soon introduce Pro plans
once general availability has been established. Stay tuned for more updates.
In the meantime, Plane remains free and unrestricted.
<br /> <br />
We{"'"}ll ensure a smooth transition from the community version to the Pro
plan for you.
</div>
<button
disabled={loader}
onClick={() => submitEmail()}
type="button"
className="mt-5 md:mt-auto whitespace-nowrap max-w-min items-center gap-x-1 rounded-md px-3 py-2 font-medium outline-none text-sm bg-custom-primary-100 text-white"
>
{loader ? "Loading.." : " Join waitlist"}
</button>
{errorMessage && (
<div
className={`mt-1 text-sm ${
errorMessage && errorMessage?.status === "success"
? "text-green-500"
: " text-red-500"
}`}
>
{errorMessage?.message}
</div>
)}
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
export default UpgradeToProModal;