From eba2f3820a07fdcb5ffb8e2a7ee3e43373475132 Mon Sep 17 00:00:00 2001 From: srinivas pendem <65014795+srinivaspendem@users.noreply.github.com> Date: Tue, 11 Jul 2023 16:27:29 +0530 Subject: [PATCH] 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 Co-authored-by: gurusainath --- apps/app/components/ui/circular-progress.tsx | 39 +++ apps/app/components/ui/index.ts | 1 + .../app/components/workspace/help-section.tsx | 291 ++++++++++++------ .../workspace/upgrade-to-pro-modal.tsx | 248 +++++++++++++++ apps/app/pages/_document.tsx | 1 + apps/app/services/web-waitlist.service.ts | 22 ++ apps/app/types/index.d.ts | 1 + apps/app/types/waitlist.d.ts | 3 + apps/app/types/workspace.d.ts | 1 + turbo.json | 4 +- 10 files changed, 519 insertions(+), 92 deletions(-) create mode 100644 apps/app/components/ui/circular-progress.tsx create mode 100644 apps/app/components/workspace/upgrade-to-pro-modal.tsx create mode 100644 apps/app/services/web-waitlist.service.ts create mode 100644 apps/app/types/waitlist.d.ts diff --git a/apps/app/components/ui/circular-progress.tsx b/apps/app/components/ui/circular-progress.tsx new file mode 100644 index 000000000..af808ee8a --- /dev/null +++ b/apps/app/components/ui/circular-progress.tsx @@ -0,0 +1,39 @@ +import React, { useEffect, useState } from "react"; + +export const CircularProgress = ({ progress }: { progress: number }) => { + const [circumference, setCircumference] = useState(0); + + useEffect(() => { + const radius = 40; + const calcCircumference = 2 * Math.PI * radius; + setCircumference(calcCircumference); + }, []); + + const progressAngle = (progress / 100) * 360 >= 360 ? 359.9 : (progress / 100) * 360; + const progressX = 50 + Math.cos((progressAngle - 90) * (Math.PI / 180)) * 40; + const progressY = 50 + Math.sin((progressAngle - 90) * (Math.PI / 180)) * 40; + + return ( +
+ + + 50 ? 1 : 0} 1 ${progressX} ${progressY} + L50 50 Z`} + strokeWidth="12" + strokeLinecap="round" + /> + +
+ ); +}; diff --git a/apps/app/components/ui/index.ts b/apps/app/components/ui/index.ts index 6eb273c4a..e66f369df 100644 --- a/apps/app/components/ui/index.ts +++ b/apps/app/components/ui/index.ts @@ -26,3 +26,4 @@ export * from "./product-updates-modal"; export * from "./integration-and-import-export-banner"; export * from "./range-datepicker"; export * from "./icon"; +export * from "./circular-progress"; diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index 54659edd6..bbb50c21f 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -1,7 +1,11 @@ import { useState, useRef, FC } from "react"; +import { useRouter } from "next/router"; + import Link from "next/link"; +import useSWR from "swr"; + // headless ui import { Transition } from "@headlessui/react"; // hooks @@ -12,8 +16,19 @@ import { ArrowLongLeftIcon, ChatBubbleOvalLeftEllipsisIcon, RocketLaunchIcon, + ArrowUpCircleIcon, + XMarkIcon, } from "@heroicons/react/24/outline"; import { QuestionMarkCircleIcon, DocumentIcon, DiscordIcon, GithubIcon } from "components/icons"; +// services +import workspaceService from "services/workspace.service"; +// fetch-keys +import { WORKSPACE_DETAILS } from "constants/fetch-keys"; +// ui +import { CircularProgress } from "components/ui"; +// components +import UpgradeToProModal from "./upgrade-to-pro-modal"; +import useUser from "hooks/use-user"; const helpOptions = [ { @@ -43,7 +58,14 @@ export interface WorkspaceHelpSectionProps { setSidebarActive: React.Dispatch>; } +type progress = { + progress: number; +}; export const WorkspaceHelpSection: FC = (props) => { + // router + const router = useRouter(); + const { workspaceSlug } = router.query; + const { setSidebarActive } = props; // theme const { collapsed: sidebarCollapse, toggleCollapsed } = useTheme(); @@ -54,105 +76,192 @@ export const WorkspaceHelpSection: FC = (props) => { // hooks useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false)); + const { user } = useUser(); + const helpOptionMode = sidebarCollapse ? "left-full" : "left-[-75px]"; - return ( -
- - - - + const [alert, setAlert] = useState(false); -
- + const [upgradeModal, setUpgradeModal] = useState(false); + + const { data: workspaceDetails } = useSWR( + workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, + workspaceSlug ? () => workspaceService.getWorkspace(workspaceSlug as string) : null + ); + const issueNumber = workspaceDetails?.total_issues || 0; + + return ( + <> + setUpgradeModal(false)} + user={user} + issueNumber={issueNumber} + /> + {!sidebarCollapse && (alert || (issueNumber && issueNumber >= 750)) ? ( + <>
= 750 + ? "bg-red-50 text-red-600 border-red-200" + : issueNumber >= 500 + ? "bg-yellow-50 text-yellow-600 border-yellow-200" + : "text-green-600" + }`} > - {helpOptions.map(({ name, Icon, href, onClick }) => { - if (href) - return ( - - + +
Free Plan
+ {issueNumber < 750 && ( +
setAlert(false)}> + +
+ )} +
+
+ This workspace has used {issueNumber} of its 1024 issues creation limit ( + {((issueNumber / 1024) * 100).toFixed(0)} + %). +
+
+ + ) : ( + "" + )} +
+ {alert || (issueNumber && issueNumber >= 750) ? ( + + ) : ( + + )} + + + + + + +
+ +
+ {helpOptions.map(({ name, Icon, href, onClick }) => { + if (href) + return ( + + + + {name} + + + ); + else + return ( + - ); - })} -
- + + ); + })} +
+ +
- + ); }; diff --git a/apps/app/components/workspace/upgrade-to-pro-modal.tsx b/apps/app/components/workspace/upgrade-to-pro-modal.tsx new file mode 100644 index 000000000..32d778fc0 --- /dev/null +++ b/apps/app/components/workspace/upgrade-to-pro-modal.tsx @@ -0,0 +1,248 @@ +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 = ({ isOpen, onClose, user, issueNumber }) => { + const [supabaseClient, setSupabaseClient] = useState(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 + ); + 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 ( + + + +
+ + +
+
+ + +
+
+
+
= 750 + ? "text-red-600" + : issueNumber >= 500 + ? "text-yellow-600" + : "text-green-600" + }`} + title="Shortcuts" + > + 100 ? 100 : (issueNumber / 1024) * 100 + } + /> +
+
+
Upgrade to pro
+
+ This workspace has used {issueNumber} of its 1024 issues creation limit ( + {((issueNumber / 1024) * 100).toFixed(2)}%). +
+
+
+ +
+
+
+
+ +
+
+
Order summary
+
+ Priority support, file uploads, and access to premium features. +
+ +
+ {proFeatures.map((feature, index) => ( +
+
+ +
+
{feature}
+
+ ))} +
+
+
+
+
+
+
Summary
+
+ +
+
+
+ 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. +

+ We{"'"}ll ensure a smooth transition from the community version to the Pro + plan for you. +
+ + {errorMessage && ( +
+ {errorMessage?.message} +
+ )} +
+
+
+
+
+
+
+
+ ); +}; + +export default UpgradeToProModal; diff --git a/apps/app/pages/_document.tsx b/apps/app/pages/_document.tsx index 24f3068c9..8958de15a 100644 --- a/apps/app/pages/_document.tsx +++ b/apps/app/pages/_document.tsx @@ -13,6 +13,7 @@ class MyDocument extends Document {