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 (
+
+
+
+ );
+};
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)) ? (
+ <>
+
+ 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 (
+
+
+
+ );
+};
+
+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 {
+
{isSessionRecorderEnabled && process.env.NEXT_PUBLIC_SESSION_RECORDER_KEY && (