Skip to content

Commit

Permalink
refactor: show banner across workspace when limits are exceeded (#3233)
Browse files Browse the repository at this point in the history
Co-authored-by: Nick Taylor <nick@nickyt.co>
  • Loading branch information
brandonroberts and nickytonline committed Apr 25, 2024
1 parent e2ebae4 commit f0d1dac
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 33 deletions.
17 changes: 4 additions & 13 deletions components/Workspaces/InsightUpgradeModal.tsx
Expand Up @@ -36,22 +36,13 @@ export default function InsightUpgradeModal({
<DialogCloseButton onClick={onClose} />
<section className="flex flex-col gap-2">
<DialogTitle className="text-xl">
{variant !== "workspace"
? "This Insight page is over the free Workspace limit"
: "Upgrade to a PRO Workspace"}
{variant !== "workspace" ? "This Workspace is over the free limit" : "Upgrade to a PRO Workspace"}
</DialogTitle>
{variant !== "workspace" ? (
<p className="text-sm text-slate-500">
Your Insight page has{" "}
<span className="font-bold">
{overLimit} {variant}
</span>{" "}
but the free Workspace only allows for{" "}
<span className="font-bold">
{variant === "repositories" ? 100 : 10} {variant}
</span>{" "}
tracked. Don&apos;t worry, your insights won&apos;t be deleted. If you want to continue using
OpenSauced you should upgrade your Workspace to a PRO Account.
Your workspace has exceeded the limit for free usage. Free Workspaces only allow for 100 repositories
and 10 contributors tracked. Don&apos;t worry, your insights won&apos;t be deleted. If you want to
continue using OpenSauced you should upgrade your Workspace to a PRO Account.
</p>
) : (
<p className="text-sm text-slate-500">
Expand Down
7 changes: 2 additions & 5 deletions components/Workspaces/WorkspaceBanner.tsx
Expand Up @@ -14,11 +14,8 @@ export default function WorkspaceBanner({ workspaceId, openModal }: WorkspaceBan
}, []);

return (
<button
onClick={openModal}
className="absolute top-0 inset-x-0 w-full h-fit px-4 py-2 bg-light-orange-10 text-white"
>
This insight page is over the free limit. <span className="font-bold underline">Upgrade to a PRO Workspace.</span>
<button onClick={openModal} className="inset-x-0 w-full h-fit px-4 py-2 bg-light-orange-10 text-white">
This workspace is over the free limit. <span className="font-bold underline">Upgrade to a PRO Workspace.</span>
</button>
);
}
9 changes: 7 additions & 2 deletions components/Workspaces/WorkspaceLayout.tsx
Expand Up @@ -2,6 +2,7 @@ import { useLocalStorage } from "react-use";
import { LuArrowRightToLine } from "react-icons/lu";
import { useOutsideClick } from "rooks";
import { useRef } from "react";
import clsx from "clsx";
import TopNav from "components/organisms/TopNav/top-nav";
import { AppSideBar } from "components/shared/AppSidebar/AppSidebar";
import { useMediaQuery } from "lib/hooks/useMediaQuery";
Expand Down Expand Up @@ -62,9 +63,13 @@ export const WorkspaceLayout = ({ workspaceId, banner, children, footer }: Works
)}
</ClientOnly>
</div>
<div className="relative flex flex-col items-center grow pt-8 md:pt-14 lg:pt-20">
<div className={clsx("flex-col items-center grow", !banner && "pt-8 md:pt-14 lg:pt-20")}>
<ClientOnly>{banner}</ClientOnly>
<div className="px-1 sm:px-2 md:px-4 xl:px-16 container w-full min-h-[100px] pb-20">{children}</div>
<div
className={clsx("px-1 sm:px-2 md:px-4 xl:px-16 container w-full min-h-[100px] pb-20", banner && "md:mt-9")}
>
{children}
</div>
</div>
</div>
{footer ? (
Expand Down
2 changes: 1 addition & 1 deletion lib/hooks/api/useIsWorkspaceUpgraded.ts
Expand Up @@ -7,5 +7,5 @@ export function useIsWorkspaceUpgraded({ workspaceId }: { workspaceId: string })
publicApiFetcher as Fetcher<Workspace, Error>
);

return { data: data?.payee_user_id !== null, error };
return { data: data?.exceeds_upgrade_limits === false, error };
}
1 change: 1 addition & 0 deletions next-types.d.ts
Expand Up @@ -512,6 +512,7 @@ interface Workspace {
is_public: boolean;
payee_user_id: string | null;
members: WorkspaceMember[];
exceeds_upgrade_limits: boolean;
}

interface WorkspaceMember {
Expand Down
41 changes: 38 additions & 3 deletions pages/workspaces/[workspaceId]/activity.tsx
@@ -1,6 +1,7 @@
import { createPagesServerClient } from "@supabase/auth-helpers-nextjs";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import dynamic from "next/dynamic";
import { useState } from "react";
import { WorkspaceLayout } from "components/Workspaces/WorkspaceLayout";
import { fetchApiData } from "helpers/fetchApiData";
Expand All @@ -17,6 +18,9 @@ import { OptionKeys } from "components/atoms/Select/multi-select";
import { useGetWorkspaceRepositories } from "lib/hooks/api/useGetWorkspaceRepositories";
import { setQueryParams } from "lib/utils/query-params";
import ClientOnly from "components/atoms/ClientOnly/client-only";
import WorkspaceBanner from "components/Workspaces/WorkspaceBanner";

const InsightUpgradeModal = dynamic(() => import("components/Workspaces/InsightUpgradeModal"));

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const supabase = createPagesServerClient(context);
Expand All @@ -31,6 +35,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
pathValidator: () => true,
});

const { data: workspaceMembers } = await fetchApiData<{ data?: WorkspaceMember[] }>({
path: `workspaces/${workspaceId}/members`,
bearerToken,
pathValidator: () => true,
});

const userId = Number(session?.user.user_metadata.sub);

const isOwner = !!(workspaceMembers?.data || []).find(
(member) => member.role === "owner" && member.user_id === userId
);

if (error) {
deleteCookie({ response: context.res, name: WORKSPACE_ID_COOKIE_NAME });

Expand All @@ -43,16 +59,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>

setCookie({ response: context.res, name: WORKSPACE_ID_COOKIE_NAME, value: workspaceId });

return { props: { workspace: data } };
return { props: { workspace: data, overLimit: !!data?.exceeds_upgrade_limits, isOwner } };
};

interface WorkspaceDashboardProps {
workspace: Workspace;
isOwner: boolean;
overLimit: boolean;
}

type OrderDirection = "ASC" | "DESC";

const WorkspaceActivityPage = ({ workspace }: WorkspaceDashboardProps) => {
const WorkspaceActivityPage = ({ workspace, isOwner, overLimit }: WorkspaceDashboardProps) => {
const router = useRouter();
const {
limit = 10,
Expand Down Expand Up @@ -85,9 +103,19 @@ const WorkspaceActivityPage = ({ workspace }: WorkspaceDashboardProps) => {
repoIds,
});

const showBanner = isOwner && overLimit;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

return (
<>
<WorkspaceLayout workspaceId={workspace.id}>
<WorkspaceLayout
workspaceId={workspace.id}
banner={
showBanner ? (
<WorkspaceBanner workspaceId={workspace.id} openModal={() => setIsInsightUpgradeModalOpen(true)} />
) : null
}
>
<WorkspaceHeader workspace={workspace} />
<div className="grid sm:flex gap-4 pt-3">
<WorkspacesTabList workspaceId={workspace.id} selectedTab={"activity"} />
Expand All @@ -108,6 +136,13 @@ const WorkspaceActivityPage = ({ workspace }: WorkspaceDashboardProps) => {
<WorkspacePullRequestTable isLoading={isLoading} data={pullRequests} meta={meta} />
</ClientOnly>
</div>
<InsightUpgradeModal
workspaceId={workspace.id}
variant="contributors"
isOpen={isInsightUpgradeModalOpen}
onClose={() => setIsInsightUpgradeModalOpen(false)}
overLimit={10}
/>
</WorkspaceLayout>
</>
);
Expand Down
Expand Up @@ -233,7 +233,7 @@ const ListActivityPage = ({
const mostActiveRef = useRef<HTMLSpanElement>(null);
const graphResizerLookup = new Map();
const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && numberOfContributors > 10;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

if (treemapRef.current) {
Expand Down
Expand Up @@ -140,7 +140,7 @@ const Highlights = ({ list, workspaceId, numberOfContributors, isOwner, highligh
const [contributor, setContributor] = useState("");
const debouncedSearchTerm = useDebounceTerm(contributor, 300);
const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && numberOfContributors > 10;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

const { data, isLoading, meta } = useFetchListContributorsHighlights({
Expand Down
Expand Up @@ -141,7 +141,7 @@ const ListsOverview = ({
const prevAllContributorCommits = prevAllContributorStats?.reduce((acc, curr) => acc + curr.commits, 0) || 0;

const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && numberOfContributors > 10;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

return (
Expand Down
38 changes: 35 additions & 3 deletions pages/workspaces/[workspaceId]/index.tsx
Expand Up @@ -23,8 +23,10 @@ import TrackedRepositoryFilter from "components/Workspaces/TrackedRepositoryFilt
import { OptionKeys } from "components/atoms/Select/multi-select";
import { WorkspaceOgImage, getWorkspaceOgImage } from "components/Workspaces/WorkspaceOgImage";
import { useHasMounted } from "lib/hooks/useHasMounted";
import WorkspaceBanner from "components/Workspaces/WorkspaceBanner";

const WorkspaceWelcomeModal = dynamic(() => import("components/Workspaces/WorkspaceWelcomeModal"));
const InsightUpgradeModal = dynamic(() => import("components/Workspaces/InsightUpgradeModal"));

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const supabase = createPagesServerClient(context);
Expand All @@ -39,6 +41,17 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
bearerToken,
pathValidator: () => true,
});
const { data: workspaceMembers } = await fetchApiData<{ data?: WorkspaceMember[] }>({
path: `workspaces/${workspaceId}/members`,
bearerToken,
pathValidator: () => true,
});

const userId = Number(session?.user.user_metadata.sub);

const isOwner = !!(workspaceMembers?.data || []).find(
(member) => member.role === "owner" && member.user_id === userId
);

if (error) {
deleteCookie({ response: context.res, name: WORKSPACE_ID_COOKIE_NAME });
Expand All @@ -57,15 +70,17 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
);

return { props: { workspace: data, ogImage: `${ogImage.href}` } };
return { props: { workspace: data, isOwner, overLimit: !!data?.exceeds_upgrade_limits, ogImage: `${ogImage.href}` } };
};

interface WorkspaceDashboardProps {
workspace: Workspace;
ogImage: string;
isOwner: boolean;
overLimit: boolean;
}

const WorkspaceDashboard = ({ workspace, ogImage }: WorkspaceDashboardProps) => {
const WorkspaceDashboard = ({ workspace, ogImage, isOwner, overLimit }: WorkspaceDashboardProps) => {
const [showWelcome, setShowWelcome] = useLocalStorage("show-welcome", true);
const hasMounted = useHasMounted();

Expand All @@ -91,6 +106,9 @@ const WorkspaceDashboard = ({ workspace, ogImage }: WorkspaceDashboardProps) =>
filteredRepositories.length > 0 ? filteredRepositories.map((repo) => repo.label) : []
);

const showBanner = isOwner && overLimit;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

useEffect(() => {
setRepoIds(
filteredRepositories.length > 0
Expand All @@ -106,7 +124,14 @@ const WorkspaceDashboard = ({ workspace, ogImage }: WorkspaceDashboardProps) =>
return (
<>
{workspace.is_public ? <WorkspaceOgImage workspace={workspace} ogImage={ogImage} /> : null}
<WorkspaceLayout workspaceId={workspace.id}>
<WorkspaceLayout
workspaceId={workspace.id}
banner={
showBanner ? (
<WorkspaceBanner workspaceId={workspace.id} openModal={() => setIsInsightUpgradeModalOpen(true)} />
) : null
}
>
<WorkspaceHeader workspace={workspace} />
<div className="grid sm:flex gap-4 pt-3">
<WorkspacesTabList workspaceId={workspace.id} selectedTab={"repositories"} />
Expand Down Expand Up @@ -161,6 +186,13 @@ const WorkspaceDashboard = ({ workspace, ogImage }: WorkspaceDashboardProps) =>
setShowWelcome(false);
}}
/>
<InsightUpgradeModal
workspaceId={workspace.id}
variant="contributors"
isOpen={isInsightUpgradeModalOpen}
onClose={() => setIsInsightUpgradeModalOpen(false)}
overLimit={10}
/>
</WorkspaceLayout>
</>
);
Expand Down
Expand Up @@ -26,7 +26,7 @@ interface InsightPageProps {
const HubPage = ({ insight, ogImage, workspaceId, owners, isOwner }: InsightPageProps) => {
const repositories = insight.repos.map((repo) => repo.repo_id);
const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && repositories.length > 100;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);
const hasMounted = useHasMounted();

Expand Down
Expand Up @@ -26,7 +26,7 @@ const HubPage = ({ insight, ogImage, workspaceId, owners, isOwner }: InsightPage
const repositories = insight.repos.map((repo) => repo.repo_id);
const [hydrated, setHydrated] = useState(false);
const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && repositories.length > 100;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

useEffect(() => {
Expand Down
Expand Up @@ -27,7 +27,7 @@ const HubPage = ({ insight, isOwner, ogImage, workspaceId, owners }: InsightPage
const [hydrated, setHydrated] = useState(false);

const { data: isWorkspaceUpgraded } = useIsWorkspaceUpgraded({ workspaceId });
const showBanner = isOwner && !isWorkspaceUpgraded && repositories.length > 100;
const showBanner = isOwner && !isWorkspaceUpgraded;
const [isInsightUpgradeModalOpen, setIsInsightUpgradeModalOpen] = useState(false);

useEffect(() => {
Expand Down

0 comments on commit f0d1dac

Please sign in to comment.