mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
feat: no login experience and prem tokens
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions
This commit is contained in:
parent
87452bb315
commit
ff4e0f9b62
68 changed files with 5914 additions and 121 deletions
156
surfsense_web/components/settings/buy-tokens-content.tsx
Normal file
156
surfsense_web/components/settings/buy-tokens-content.tsx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"use client";
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Minus, Plus } from "lucide-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { stripeApiService } from "@/lib/apis/stripe-api.service";
|
||||
import { AppError } from "@/lib/error";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TOKEN_PACK_SIZE = 1_000_000;
|
||||
const PRICE_PER_PACK_USD = 1;
|
||||
const PRESET_MULTIPLIERS = [1, 2, 5, 10, 25, 50] as const;
|
||||
|
||||
export function BuyTokensContent() {
|
||||
const params = useParams();
|
||||
const searchSpaceId = Number(params?.search_space_id);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
|
||||
const { data: tokenStatus } = useQuery({
|
||||
queryKey: ["token-status"],
|
||||
queryFn: () => stripeApiService.getTokenStatus(),
|
||||
});
|
||||
|
||||
const purchaseMutation = useMutation({
|
||||
mutationFn: stripeApiService.createTokenCheckoutSession,
|
||||
onSuccess: (response) => {
|
||||
window.location.assign(response.checkout_url);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof AppError && error.message) {
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
toast.error("Failed to start checkout. Please try again.");
|
||||
},
|
||||
});
|
||||
|
||||
const totalTokens = quantity * TOKEN_PACK_SIZE;
|
||||
const totalPrice = quantity * PRICE_PER_PACK_USD;
|
||||
|
||||
if (tokenStatus && !tokenStatus.token_buying_enabled) {
|
||||
return (
|
||||
<div className="w-full space-y-3 text-center">
|
||||
<h2 className="text-xl font-bold tracking-tight">Buy Premium Tokens</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Token purchases are temporarily unavailable.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const usagePercentage = tokenStatus
|
||||
? Math.min(
|
||||
(tokenStatus.premium_tokens_used / Math.max(tokenStatus.premium_tokens_limit, 1)) * 100,
|
||||
100
|
||||
)
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-5">
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-bold tracking-tight">Buy Premium Tokens</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">$1 per 1M tokens, pay as you go</p>
|
||||
</div>
|
||||
|
||||
{tokenStatus && (
|
||||
<div className="rounded-lg border bg-muted/20 p-3 space-y-1.5">
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<span className="text-muted-foreground">
|
||||
{tokenStatus.premium_tokens_used.toLocaleString()} /{" "}
|
||||
{tokenStatus.premium_tokens_limit.toLocaleString()} premium tokens
|
||||
</span>
|
||||
<span className="font-medium">{usagePercentage.toFixed(0)}%</span>
|
||||
</div>
|
||||
<Progress value={usagePercentage} className="h-1.5" />
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{tokenStatus.premium_tokens_remaining.toLocaleString()} tokens remaining
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuantity((q) => Math.max(1, q - 1))}
|
||||
disabled={quantity <= 1 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<span className="min-w-32 text-center text-lg font-semibold tabular-nums">
|
||||
{(totalTokens / 1_000_000).toFixed(0)}M tokens
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setQuantity((q) => Math.min(100, q + 1))}
|
||||
disabled={quantity >= 100 || purchaseMutation.isPending}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted disabled:opacity-40"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-1.5">
|
||||
{PRESET_MULTIPLIERS.map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
type="button"
|
||||
onClick={() => setQuantity(m)}
|
||||
disabled={purchaseMutation.isPending}
|
||||
className={cn(
|
||||
"rounded-md border px-2.5 py-1 text-xs font-medium tabular-nums transition-colors disabled:opacity-60",
|
||||
quantity === m
|
||||
? "border-purple-500 bg-purple-500/10 text-purple-600 dark:text-purple-400"
|
||||
: "border-border hover:border-purple-500/40 hover:bg-muted/40"
|
||||
)}
|
||||
>
|
||||
{m}M
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between rounded-lg border bg-muted/30 px-3 py-2">
|
||||
<span className="text-sm font-medium tabular-nums">
|
||||
{(totalTokens / 1_000_000).toFixed(0)}M premium tokens
|
||||
</span>
|
||||
<span className="text-sm font-semibold tabular-nums">${totalPrice}</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="w-full bg-purple-600 text-white hover:bg-purple-700"
|
||||
disabled={purchaseMutation.isPending}
|
||||
onClick={() => purchaseMutation.mutate({ quantity, search_space_id: searchSpaceId })}
|
||||
>
|
||||
{purchaseMutation.isPending ? (
|
||||
<>
|
||||
<Spinner size="xs" />
|
||||
Redirecting
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Buy {(totalTokens / 1_000_000).toFixed(0)}M Tokens for ${totalPrice}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-center text-[11px] text-muted-foreground">Secure checkout via Stripe</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue