mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-23 19:05:16 +02:00
refactor: implement tab navigation in BuyMorePage and enhance button styles in BuyPagesContent and BuyTokensContent
This commit is contained in:
parent
88a43cdd65
commit
10527ddb7c
6 changed files with 114 additions and 96 deletions
|
|
@ -4,8 +4,7 @@ import { motion } from "motion/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { BuyPagesContent } from "@/components/settings/buy-pages-content";
|
import { BuyPagesContent } from "@/components/settings/buy-pages-content";
|
||||||
import { BuyTokensContent } from "@/components/settings/buy-tokens-content";
|
import { BuyTokensContent } from "@/components/settings/buy-tokens-content";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: "pages", label: "Pages" },
|
{ id: "pages", label: "Pages" },
|
||||||
|
|
@ -22,29 +21,34 @@ export default function BuyMorePage() {
|
||||||
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 select-none space-y-6"
|
className="w-full select-none"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center rounded-lg border bg-muted/30 p-1">
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setActiveTab(value as TabId);
|
||||||
|
}}
|
||||||
|
className="relative min-h-[37rem] w-full"
|
||||||
|
>
|
||||||
|
<TabsList className="absolute top-20 left-1/2 -translate-x-1/2 rounded-xl bg-accent p-1">
|
||||||
{TABS.map((tab) => (
|
{TABS.map((tab) => (
|
||||||
<Button
|
<TabsTrigger
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
type="button"
|
value={tab.id}
|
||||||
variant="ghost"
|
className="h-8 rounded-lg px-4 text-sm font-semibold text-accent-foreground transition-colors hover:bg-transparent hover:text-white data-[state=active]:bg-[#4a4a4a] data-[state=active]:text-white data-[state=active]:shadow-none"
|
||||||
size="sm"
|
|
||||||
onClick={() => setActiveTab(tab.id)}
|
|
||||||
className={cn(
|
|
||||||
"h-auto flex-1 px-3 py-1.5 text-sm",
|
|
||||||
activeTab === tab.id
|
|
||||||
? "bg-background text-foreground shadow-sm"
|
|
||||||
: "text-muted-foreground hover:text-accent-foreground"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</Button>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
</div>
|
</TabsList>
|
||||||
|
|
||||||
{activeTab === "pages" ? <BuyPagesContent /> : <BuyTokensContent />}
|
<TabsContent value="pages" className="mt-0 flex min-h-[37rem] items-center pt-14">
|
||||||
|
<BuyPagesContent />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="tokens" className="mt-0 flex min-h-[37rem] items-center pt-14">
|
||||||
|
<BuyTokensContent />
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion } from "motion/react";
|
|
||||||
import { MorePagesContent } from "@/components/settings/more-pages-content";
|
import { MorePagesContent } from "@/components/settings/more-pages-content";
|
||||||
|
|
||||||
export default function MorePagesPage() {
|
export default function MorePagesPage() {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div className="w-full select-none space-y-6">
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
className="w-full select-none space-y-6"
|
|
||||||
>
|
|
||||||
<MorePagesContent />
|
<MorePagesContent />
|
||||||
</motion.div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import {
|
||||||
Wrench,
|
Wrench,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
@ -271,24 +272,39 @@ const ConnectToolsBanner: FC<{
|
||||||
if (typeof window === "undefined") return false;
|
if (typeof window === "undefined") return false;
|
||||||
return localStorage.getItem(BANNER_DISMISSED_KEY) === "true";
|
return localStorage.getItem(BANNER_DISMISSED_KEY) === "true";
|
||||||
});
|
});
|
||||||
|
const [dismissRequested, setDismissRequested] = useState(false);
|
||||||
|
|
||||||
const hasConnectors = (connectors?.length ?? 0) > 0;
|
const hasConnectors = (connectors?.length ?? 0) > 0;
|
||||||
const isVisible = !dismissed && !hasConnectors && isThreadEmpty;
|
const isVisible = !dismissed && !hasConnectors && isThreadEmpty;
|
||||||
|
const shouldShowTray = isVisible && !dismissRequested;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onVisibleChange?.(isVisible);
|
onVisibleChange?.(isVisible);
|
||||||
}, [isVisible, onVisibleChange]);
|
}, [isVisible, onVisibleChange]);
|
||||||
|
|
||||||
if (!isVisible) return null;
|
|
||||||
|
|
||||||
const handleDismiss = (e: React.MouseEvent) => {
|
const handleDismiss = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setDismissed(true);
|
setDismissRequested(true);
|
||||||
localStorage.setItem(BANNER_DISMISSED_KEY, "true");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-0 -mt-5 flex min-w-0 items-center gap-2 rounded-b-3xl border border-input bg-muted/40 px-4 pt-7 pb-3 shadow-sm shadow-black/5 dark:shadow-black/10">
|
<AnimatePresence
|
||||||
|
initial={false}
|
||||||
|
onExitComplete={() => {
|
||||||
|
if (!dismissRequested) return;
|
||||||
|
setDismissed(true);
|
||||||
|
localStorage.setItem(BANNER_DISMISSED_KEY, "true");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shouldShowTray ? (
|
||||||
|
<motion.div
|
||||||
|
key="connect-tools-tray"
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -14 }}
|
||||||
|
transition={{ duration: 0.18, ease: "easeOut" }}
|
||||||
|
className="relative z-0 -mt-5 flex min-w-0 items-center gap-2 rounded-b-3xl border border-input bg-muted/40 px-4 pt-7 pb-3 shadow-sm shadow-black/5 dark:shadow-black/10"
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -323,7 +339,9 @@ const ConnectToolsBanner: FC<{
|
||||||
>
|
>
|
||||||
<X className="size-3.5" />
|
<X className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</motion.div>
|
||||||
|
) : null}
|
||||||
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,11 @@ export function BuyPagesContent() {
|
||||||
<div className="flex items-center justify-center gap-3">
|
<div className="flex items-center justify-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
||||||
disabled={quantity <= 1 || purchaseMutation.isPending}
|
disabled={quantity <= 1 || purchaseMutation.isPending}
|
||||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
className="size-8 text-muted-foreground shadow-none transition-colors hover:bg-muted hover:text-white disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Minus className="h-3.5 w-3.5" />
|
<Minus className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -87,11 +87,11 @@ export function BuyPagesContent() {
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
||||||
disabled={quantity >= 100 || purchaseMutation.isPending}
|
disabled={quantity >= 100 || purchaseMutation.isPending}
|
||||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
className="size-8 text-muted-foreground shadow-none transition-colors hover:bg-muted hover:text-white disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -107,10 +107,10 @@ export function BuyPagesContent() {
|
||||||
onClick={() => setQuantity(m)}
|
onClick={() => setQuantity(m)}
|
||||||
disabled={purchaseMutation.isPending}
|
disabled={purchaseMutation.isPending}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-auto rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors hover:text-foreground disabled:opacity-60",
|
"h-auto rounded-md px-2.5 py-1 text-xs font-medium tabular-nums transition-colors disabled:opacity-60",
|
||||||
quantity === m
|
quantity === m
|
||||||
? "border-emerald-500 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
|
? "bg-accent text-accent-foreground"
|
||||||
: "border-border hover:border-emerald-500/40 hover:bg-muted/40"
|
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{(m * PAGE_PACK_SIZE).toLocaleString()}
|
{(m * PAGE_PACK_SIZE).toLocaleString()}
|
||||||
|
|
@ -126,7 +126,7 @@ export function BuyPagesContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-emerald-600 text-white hover:bg-emerald-700"
|
className="w-full"
|
||||||
disabled={purchaseMutation.isPending || !hasValidSearchSpace}
|
disabled={purchaseMutation.isPending || !hasValidSearchSpace}
|
||||||
onClick={handleBuyNow}
|
onClick={handleBuyNow}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export function BuyTokensContent() {
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">{usagePercentage.toFixed(0)}%</span>
|
<span className="font-medium">{usagePercentage.toFixed(0)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={usagePercentage} className="h-1.5" />
|
<Progress value={usagePercentage} className="h-1.5 [&>div]:bg-purple-500" />
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{formatUsd(remaining)} of credit remaining
|
{formatUsd(remaining)} of credit remaining
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -107,11 +107,11 @@ export function BuyTokensContent() {
|
||||||
<div className="flex items-center justify-center gap-3">
|
<div className="flex items-center justify-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
||||||
disabled={quantity <= 1 || purchaseMutation.isPending}
|
disabled={quantity <= 1 || purchaseMutation.isPending}
|
||||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
className="size-8 text-muted-foreground shadow-none transition-colors hover:bg-muted hover:text-white disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Minus className="h-3.5 w-3.5" />
|
<Minus className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -120,11 +120,11 @@ export function BuyTokensContent() {
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
||||||
disabled={quantity >= 100 || purchaseMutation.isPending}
|
disabled={quantity >= 100 || purchaseMutation.isPending}
|
||||||
className="size-8 shadow-none transition-colors hover:bg-muted disabled:opacity-40"
|
className="size-8 text-muted-foreground shadow-none transition-colors hover:bg-muted hover:text-white disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -139,10 +139,10 @@ export function BuyTokensContent() {
|
||||||
onClick={() => setQuantity(m)}
|
onClick={() => setQuantity(m)}
|
||||||
disabled={purchaseMutation.isPending}
|
disabled={purchaseMutation.isPending}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-auto rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors hover:text-foreground disabled:opacity-60",
|
"h-auto rounded-md px-2.5 py-1 text-xs font-medium tabular-nums transition-colors disabled:opacity-60",
|
||||||
quantity === m
|
quantity === m
|
||||||
? "border-purple-500 bg-purple-500/10 text-purple-600 dark:text-purple-400"
|
? "bg-accent text-accent-foreground"
|
||||||
: "border-border hover:border-purple-500/40 hover:bg-muted/40"
|
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${m}
|
${m}
|
||||||
|
|
@ -158,7 +158,7 @@ export function BuyTokensContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-purple-600 text-white hover:bg-purple-700"
|
className="w-full"
|
||||||
disabled={purchaseMutation.isPending}
|
disabled={purchaseMutation.isPending}
|
||||||
onClick={() => purchaseMutation.mutate({ quantity, search_space_id: searchSpaceId })}
|
onClick={() => purchaseMutation.mutate({ quantity, search_space_id: searchSpaceId })}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -76,15 +76,17 @@ export function MorePagesContent() {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="text-sm font-semibold">Earn Bonus Pages</h3>
|
<h3 className="text-sm font-semibold">Earn Bonus Pages</h3>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Card>
|
<div className="space-y-1.5">
|
||||||
|
{["github", "reddit", "discord"].map((task) => (
|
||||||
|
<Card key={task} className="bg-transparent">
|
||||||
<CardContent className="flex items-center gap-3 p-3">
|
<CardContent className="flex items-center gap-3 p-3">
|
||||||
<Skeleton className="h-8 w-8 rounded-full" />
|
<Skeleton className="h-8 w-8 rounded-full bg-muted" />
|
||||||
<div className="flex-1 space-y-2">
|
<Skeleton className="h-4 flex-1 bg-muted" />
|
||||||
<Skeleton className="h-4 w-3/4" />
|
<Skeleton className="h-8 w-16 bg-muted" />
|
||||||
</div>
|
|
||||||
<Skeleton className="h-8 w-16" />
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{data?.tasks.map((task) => (
|
{data?.tasks.map((task) => (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue