mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-24 21:38:09 +02:00
feat: enhance MorePagesPage and PageUsageDisplay with PRO upgrade options and improved UI elements
This commit is contained in:
parent
62c9880f11
commit
ddb070bca8
2 changed files with 88 additions and 65 deletions
|
|
@ -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'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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue