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 ? ( - - - -
- - -
- -
-
- ) : ( -
- {data?.tasks.map((task) => ( - - -
- {task.completed ? : } -
-
-

- {task.title} -

-

+{task.pages_reward} pages

-
- -
-
- ))} -
- )} - - {/* 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. - -
- - open && trackIncentiveContactOpened()}> - - - - - - Get in Touch - Pick the option that works best for you. - -
- - -
-
-
-
-
+
); 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 ? ( + + + +
+ + +
+ +
+
+ ) : ( +
+ {data?.tasks.map((task) => ( + + +
+ {task.completed ? : } +
+
+

+ {task.title} +

+

+{task.pages_reward} pages

+
+ +
+
+ ))} +
+ )} + + + + + +
+ + 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. + +
+ + open && trackIncentiveContactOpened()}> + + + + + + Get in Touch + Pick the option that works best for you. + +
+ + +
+
+
+
+
+
+ ); +} 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 ( + + e.preventDefault()} + > + Get More Pages +
+ +
+
+
+ ); +}