feat: enhance MorePagesPage and PageUsageDisplay with PRO upgrade options and improved UI elements

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-03-09 17:05:15 -07:00
parent 62c9880f11
commit ddb070bca8
2 changed files with 88 additions and 65 deletions

View file

@ -2,13 +2,14 @@
import { IconCalendar, IconMailFilled } from "@tabler/icons-react"; import { IconCalendar, IconMailFilled } from "@tabler/icons-react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Check, ExternalLink, Gift, Mail, Star } from "lucide-react"; import { Check, ExternalLink, Gift, Mail, Star, Zap } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
import { useEffect } from "react"; import { useEffect } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -33,29 +34,24 @@ import { cn } from "@/lib/utils";
export default function MorePagesPage() { export default function MorePagesPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// Track page view on mount
useEffect(() => { useEffect(() => {
trackIncentivePageViewed(); trackIncentivePageViewed();
}, []); }, []);
// Fetch tasks from API
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ["incentive-tasks"], queryKey: ["incentive-tasks"],
queryFn: () => incentiveTasksApiService.getTasks(), queryFn: () => incentiveTasksApiService.getTasks(),
}); });
// Mutation to complete a task
const completeMutation = useMutation({ const completeMutation = useMutation({
mutationFn: incentiveTasksApiService.completeTask, mutationFn: incentiveTasksApiService.completeTask,
onSuccess: (response, taskType) => { onSuccess: (response, taskType) => {
if (response.success) { if (response.success) {
toast.success(response.message); toast.success(response.message);
// Track task completion
const task = data?.tasks.find((t) => t.task_type === taskType); const task = data?.tasks.find((t) => t.task_type === taskType);
if (task) { if (task) {
trackIncentiveTaskCompleted(taskType, task.pages_reward); trackIncentiveTaskCompleted(taskType, task.pages_reward);
} }
// Invalidate queries to refresh data
queryClient.invalidateQueries({ queryKey: ["incentive-tasks"] }); queryClient.invalidateQueries({ queryKey: ["incentive-tasks"] });
queryClient.invalidateQueries({ queryKey: ["user"] }); queryClient.invalidateQueries({ queryKey: ["user"] });
} }
@ -72,21 +68,21 @@ export default function MorePagesPage() {
} }
}; };
const allCompleted = data?.tasks.every((t) => t.completed) ?? false;
return ( return (
<div className="flex min-h-[calc(100vh-64px)] select-none items-center justify-center px-4 py-8"> <div className="flex min-h-[calc(100vh-64px)] select-none items-center justify-center px-4 py-8">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="w-full max-w-md" className="w-full max-w-md space-y-6"
> >
{/* Header */} {/* Header */}
<div className="mb-6 text-center"> <div className="text-center">
<Gift className="mx-auto mb-3 h-8 w-8 text-primary" /> <Gift className="mx-auto mb-3 h-8 w-8 text-primary" />
<h2 className="text-xl font-bold tracking-tight">Get More Pages</h2> <h2 className="text-xl font-bold tracking-tight">Get More Pages</h2>
<p className="text-sm text-muted-foreground">Complete tasks to earn additional pages</p> <p className="mt-1 text-sm text-muted-foreground">
Complete tasks to earn additional pages
</p>
</div> </div>
{/* Tasks */} {/* Tasks */}
@ -112,26 +108,37 @@ export default function MorePagesPage() {
<div <div
className={cn( className={cn(
"flex h-9 w-9 shrink-0 items-center justify-center rounded-full", "flex h-9 w-9 shrink-0 items-center justify-center rounded-full",
task.completed ? "bg-primary text-primary-foreground" : "bg-muted" task.completed
? "bg-primary text-primary-foreground"
: "bg-muted"
)} )}
> >
{task.completed ? <Check className="h-4 w-4" /> : <Star className="h-4 w-4" />} {task.completed ? (
<Check className="h-4 w-4" />
) : (
<Star className="h-4 w-4" />
)}
</div> </div>
<div className="flex-1 min-w-0"> <div className="min-w-0 flex-1">
<p <p
className={cn( className={cn(
"text-sm font-medium", "text-sm font-medium",
task.completed && "text-muted-foreground line-through" task.completed &&
"text-muted-foreground line-through"
)} )}
> >
{task.title} {task.title}
</p> </p>
<p className="text-xs text-muted-foreground">+{task.pages_reward} pages</p> <p className="text-xs text-muted-foreground">
+{task.pages_reward} pages
</p>
</div> </div>
<Button <Button
variant={task.completed ? "ghost" : "outline"} variant={task.completed ? "ghost" : "outline"}
size="sm" size="sm"
disabled={task.completed || completeMutation.isPending} disabled={
task.completed || completeMutation.isPending
}
onClick={() => handleTaskClick(task)} onClick={() => handleTaskClick(task)}
asChild={!task.completed} asChild={!task.completed}
> >
@ -161,50 +168,60 @@ export default function MorePagesPage() {
</div> </div>
)} )}
{/* Contact */} {/* PRO Upgrade */}
<Separator className="my-6" /> <Separator />
<div className="text-center">
<p className="mb-3 text-sm text-muted-foreground"> <Card className="overflow-hidden border-emerald-500/20">
{allCompleted ? "Thanks! Need even more pages?" : "Need more pages?"} <CardHeader className="pb-2">
</p> <div className="flex items-center gap-2">
<Dialog onOpenChange={(open) => open && trackIncentiveContactOpened()}> <Zap className="h-4 w-4 text-emerald-500" />
<DialogTrigger asChild> <CardTitle className="text-base">Upgrade to PRO</CardTitle>
<Button variant="outline" size="sm" className="gap-2"> <Badge className="bg-emerald-600 text-white border-transparent hover:bg-emerald-600">
<Mail className="h-4 w-4" /> FREE
Contact Us </Badge>
</Button> </div>
</DialogTrigger> <CardDescription>
<DialogContent className="select-none sm:max-w-md"> For a limited time, get <span className="font-semibold text-foreground">6,000 additional pages</span> at
<DialogHeader> no cost. Contact us and we&apos;ll upgrade your account instantly.
<DialogTitle>Contact Us</DialogTitle> </CardDescription>
<DialogDescription>Schedule a meeting or send us an email.</DialogDescription> </CardHeader>
</DialogHeader> <CardFooter className="pt-2">
<div className="flex flex-col items-center gap-4 py-4"> <Dialog onOpenChange={(open) => open && trackIncentiveContactOpened()}>
<Link <DialogTrigger asChild>
href="https://cal.com/mod-rohan" <Button className="w-full bg-emerald-600 text-white hover:bg-emerald-700">
target="_blank" <Mail className="h-4 w-4" />
rel="noopener noreferrer" Contact Us to Upgrade
className="flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition hover:bg-primary/90" </Button>
> </DialogTrigger>
<IconCalendar className="h-4 w-4" /> <DialogContent className="select-none sm:max-w-sm">
Schedule a Meeting <DialogHeader>
</Link> <DialogTitle>Get in Touch</DialogTitle>
<div className="flex items-center gap-2 text-muted-foreground"> <DialogDescription>
<span className="h-px w-8 bg-border" /> Pick the option that works best for you.
<span className="text-xs">or</span> </DialogDescription>
<span className="h-px w-8 bg-border" /> </DialogHeader>
<div className="flex flex-col gap-2">
<Button asChild>
<Link
href="https://cal.com/mod-rohan"
target="_blank"
rel="noopener noreferrer"
>
<IconCalendar className="h-4 w-4" />
Schedule a Meeting
</Link>
</Button>
<Button variant="outline" asChild>
<Link href="mailto:rohan@surfsense.com">
<IconMailFilled className="h-4 w-4" />
rohan@surfsense.com
</Link>
</Button>
</div> </div>
<Link </DialogContent>
href="mailto:rohan@surfsense.com" </Dialog>
className="flex items-center gap-2 text-sm text-muted-foreground transition hover:text-foreground" </CardFooter>
> </Card>
<IconMailFilled className="h-4 w-4" />
rohan@surfsense.com
</Link>
</div>
</DialogContent>
</Dialog>
</div>
</motion.div> </motion.div>
</div> </div>
); );

View file

@ -1,8 +1,9 @@
"use client"; "use client";
import { Plus } from "lucide-react"; import { Zap } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
interface PageUsageDisplayProps { interface PageUsageDisplayProps {
@ -27,10 +28,15 @@ export function PageUsageDisplay({ pagesUsed, pagesLimit }: PageUsageDisplayProp
<Progress value={usagePercentage} className="h-1.5" /> <Progress value={usagePercentage} className="h-1.5" />
<Link <Link
href={`/dashboard/${searchSpaceId}/more-pages`} href={`/dashboard/${searchSpaceId}/more-pages`}
className="flex items-center gap-1.5 text-[10px] text-muted-foreground hover:text-primary transition-colors" className="group flex items-center justify-between rounded-md px-1.5 py-1 -mx-1.5 transition-colors hover:bg-accent"
> >
<Plus className="h-3 w-3 shrink-0" /> <span className="flex items-center gap-1.5 text-xs text-muted-foreground group-hover:text-accent-foreground">
<span>Get More Pages</span> <Zap className="h-3 w-3 shrink-0" />
Upgrade to PRO
</span>
<Badge className="h-4 rounded px-1 text-[10px] font-semibold leading-none bg-emerald-600 text-white border-transparent hover:bg-emerald-600">
FREE
</Badge>
</Link> </Link>
</div> </div>
</div> </div>