diff --git a/surfsense_web/app/dashboard/[search_space_id]/more-pages/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/more-pages/page.tsx
index 27c451d2f..448706caf 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/more-pages/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/more-pages/page.tsx
@@ -1,80 +1,9 @@
"use client";
-import { IconCalendar, IconMailFilled } from "@tabler/icons-react";
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { Check, ExternalLink, Gift, Mail, Star, Zap } from "lucide-react";
import { motion } from "motion/react";
-import Link from "next/link";
-import { useEffect } from "react";
-import { toast } from "sonner";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import { Separator } from "@/components/ui/separator";
-import { Skeleton } from "@/components/ui/skeleton";
-import { Spinner } from "@/components/ui/spinner";
-import type { IncentiveTaskInfo } from "@/contracts/types/incentive-tasks.types";
-import { incentiveTasksApiService } from "@/lib/apis/incentive-tasks-api.service";
-import {
- trackIncentiveContactOpened,
- trackIncentivePageViewed,
- trackIncentiveTaskClicked,
- trackIncentiveTaskCompleted,
-} from "@/lib/posthog/events";
-import { cn } from "@/lib/utils";
+import { MorePagesContent } from "@/components/settings/more-pages-content";
export default function MorePagesPage() {
- const queryClient = useQueryClient();
-
- useEffect(() => {
- trackIncentivePageViewed();
- }, []);
-
- const { data, isLoading } = useQuery({
- queryKey: ["incentive-tasks"],
- queryFn: () => incentiveTasksApiService.getTasks(),
- });
-
- const completeMutation = useMutation({
- mutationFn: incentiveTasksApiService.completeTask,
- onSuccess: (response, taskType) => {
- if (response.success) {
- toast.success(response.message);
- const task = data?.tasks.find((t) => t.task_type === taskType);
- if (task) {
- trackIncentiveTaskCompleted(taskType, task.pages_reward);
- }
- queryClient.invalidateQueries({ queryKey: ["incentive-tasks"] });
- queryClient.invalidateQueries({ queryKey: ["user"] });
- }
- },
- onError: () => {
- toast.error("Failed to complete task. Please try again.");
- },
- });
-
- const handleTaskClick = (task: IncentiveTaskInfo) => {
- if (!task.completed) {
- trackIncentiveTaskClicked(task.task_type);
- completeMutation.mutate(task.task_type);
- }
- };
-
return (
- {/* Header */}
-
-
-
Get More Pages
-
- Complete tasks to earn additional pages
-
-
-
- {/* Tasks */}
- {isLoading ? (
-
-
-
-
-
-
-
-
-
-
- ) : (
-
- )}
-
- {/* PRO Upgrade */}
-
-
-
-
-
-
- Upgrade to PRO
-
- FREE
-
-
-
- For a limited time, get{" "}
- 6,000 additional pages at no
- cost. Contact us and we'll upgrade your account instantly.
-
-
-
-
-
-
+
);
diff --git a/surfsense_web/atoms/settings/settings-dialog.atoms.ts b/surfsense_web/atoms/settings/settings-dialog.atoms.ts
index 3b49f1f06..282cc65b3 100644
--- a/surfsense_web/atoms/settings/settings-dialog.atoms.ts
+++ b/surfsense_web/atoms/settings/settings-dialog.atoms.ts
@@ -21,3 +21,5 @@ export const userSettingsDialogAtom = atom({
});
export const teamDialogAtom = atom(false);
+
+export const morePagesDialogAtom = atom(false);
diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
index 10a5bacfe..633ce9552 100644
--- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
+++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
@@ -15,6 +15,7 @@ import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import {
+ morePagesDialogAtom,
searchSpaceSettingsDialogAtom,
teamDialogAtom,
userSettingsDialogAtom,
@@ -40,7 +41,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
-import { isPageLimitExceededMetadata } from "@/contracts/types/inbox.types";
+
import { useAnnouncements } from "@/hooks/use-announcements";
import { useDocumentsProcessing } from "@/hooks/use-documents-processing";
import { useInbox } from "@/hooks/use-inbox";
@@ -52,6 +53,7 @@ import { deleteThread, fetchThreads, updateThread } from "@/lib/chat/thread-pers
import { cleanupElectric } from "@/lib/electric/client";
import { resetUser, trackLogout } from "@/lib/posthog/events";
import { cacheKeys } from "@/lib/query-client/cache-keys";
+import { MorePagesDialog } from "@/components/settings/more-pages-dialog";
import { SearchSpaceSettingsDialog } from "@/components/settings/search-space-settings-dialog";
import { TeamDialog } from "@/components/settings/team-dialog";
import { UserSettingsDialog } from "@/components/settings/user-settings-dialog";
@@ -201,6 +203,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
const seenPageLimitNotifications = useRef>(new Set());
const isInitialLoad = useRef(true);
+ const setMorePagesOpen = useSetAtom(morePagesDialogAtom);
+
// Effect to show toast for new page_limit_exceeded notifications
useEffect(() => {
if (statusInbox.loading) return;
@@ -224,21 +228,17 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
for (const notification of newNotifications) {
seenPageLimitNotifications.current.add(notification.id);
- const actionUrl = isPageLimitExceededMetadata(notification.metadata)
- ? notification.metadata.action_url
- : `/dashboard/${searchSpaceId}/more-pages`;
-
toast.error(notification.title, {
description: notification.message,
duration: 8000,
icon: ,
action: {
label: "View Plans",
- onClick: () => router.push(actionUrl),
+ onClick: () => setMorePagesOpen(true),
},
});
}
- }, [statusInbox.inboxItems, statusInbox.loading, searchSpaceId, router]);
+ }, [statusInbox.inboxItems, statusInbox.loading, searchSpaceId, setMorePagesOpen]);
// Delete dialogs state
const [showDeleteChatDialog, setShowDeleteChatDialog] = useState(false);
@@ -951,6 +951,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
+
>
);
}
diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx
index 991b00b53..5141ffe9e 100644
--- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx
+++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx
@@ -362,6 +362,9 @@ export function LayoutShell({
{usagePercentage.toFixed(0)}%
- setMorePagesOpen(true)}
+ className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 -mx-1.5 transition-colors hover:bg-accent"
>
@@ -37,7 +37,7 @@ export function PageUsageDisplay({ pagesUsed, pagesLimit }: PageUsageDisplayProp
FREE
-
+
);
diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
index 8091e21d7..8a9376af3 100644
--- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
@@ -54,7 +54,6 @@ interface SidebarProps {
isLoadingChats?: boolean;
disableTooltips?: boolean;
sidebarWidth?: number;
- onResizeMouseDown?: (e: React.MouseEvent) => void;
isResizing?: boolean;
}
diff --git a/surfsense_web/components/settings/more-pages-content.tsx b/surfsense_web/components/settings/more-pages-content.tsx
new file mode 100644
index 000000000..55447a298
--- /dev/null
+++ b/surfsense_web/components/settings/more-pages-content.tsx
@@ -0,0 +1,212 @@
+"use client";
+
+import { IconCalendar, IconMailFilled } from "@tabler/icons-react";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { Check, ExternalLink, Gift, Mail, Star, Zap } from "lucide-react";
+import Link from "next/link";
+import { useEffect } from "react";
+import { toast } from "sonner";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Separator } from "@/components/ui/separator";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Spinner } from "@/components/ui/spinner";
+import type { IncentiveTaskInfo } from "@/contracts/types/incentive-tasks.types";
+import { incentiveTasksApiService } from "@/lib/apis/incentive-tasks-api.service";
+import {
+ trackIncentiveContactOpened,
+ trackIncentivePageViewed,
+ trackIncentiveTaskClicked,
+ trackIncentiveTaskCompleted,
+} from "@/lib/posthog/events";
+import { cn } from "@/lib/utils";
+
+export function MorePagesContent() {
+ const queryClient = useQueryClient();
+
+ useEffect(() => {
+ trackIncentivePageViewed();
+ }, []);
+
+ const { data, isLoading } = useQuery({
+ queryKey: ["incentive-tasks"],
+ queryFn: () => incentiveTasksApiService.getTasks(),
+ });
+
+ const completeMutation = useMutation({
+ mutationFn: incentiveTasksApiService.completeTask,
+ onSuccess: (response, taskType) => {
+ if (response.success) {
+ toast.success(response.message);
+ const task = data?.tasks.find((t) => t.task_type === taskType);
+ if (task) {
+ trackIncentiveTaskCompleted(taskType, task.pages_reward);
+ }
+ queryClient.invalidateQueries({ queryKey: ["incentive-tasks"] });
+ queryClient.invalidateQueries({ queryKey: ["user"] });
+ }
+ },
+ onError: () => {
+ toast.error("Failed to complete task. Please try again.");
+ },
+ });
+
+ const handleTaskClick = (task: IncentiveTaskInfo) => {
+ if (!task.completed) {
+ trackIncentiveTaskClicked(task.task_type);
+ completeMutation.mutate(task.task_type);
+ }
+ };
+
+ return (
+
+
+
+
Get More Pages
+
+ Complete tasks to earn additional pages
+
+
+
+ {isLoading ? (
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ Upgrade to PRO
+
+ FREE
+
+
+
+ For a limited time, get{" "}
+ 6,000 additional pages at no
+ cost. Contact us and we'll upgrade your account instantly.
+
+
+
+
+
+
+
+ );
+}
diff --git a/surfsense_web/components/settings/more-pages-dialog.tsx b/surfsense_web/components/settings/more-pages-dialog.tsx
new file mode 100644
index 000000000..450079f36
--- /dev/null
+++ b/surfsense_web/components/settings/more-pages-dialog.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { useAtom } from "jotai";
+import { morePagesDialogAtom } from "@/atoms/settings/settings-dialog.atoms";
+import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
+import { MorePagesContent } from "./more-pages-content";
+
+export function MorePagesDialog() {
+ const [open, setOpen] = useAtom(morePagesDialogAtom);
+
+ return (
+
+ );
+}