show credit usage to the user

This commit is contained in:
Abi Raja 2023-12-06 10:09:09 -05:00
parent 2ba2a79e2f
commit c275e945fc
4 changed files with 145 additions and 3 deletions

View File

@ -20,6 +20,7 @@
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",

View File

@ -1,20 +1,88 @@
import { useEffect, useState } from "react";
import { Settings } from "../../types"; import { Settings } from "../../types";
import { Button } from "../ui/button";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Label } from "../ui/label"; import { Label } from "../ui/label";
import useThrottle from "../../hooks/useThrottle";
import { Progress } from "../ui/progress";
import { PICO_BACKEND_FORM_SECRET } from "../../config";
interface Props { interface Props {
settings: Settings; settings: Settings;
setSettings: React.Dispatch<React.SetStateAction<Settings>>; setSettings: React.Dispatch<React.SetStateAction<Settings>>;
} }
interface UsageResponse {
used_credits: number;
total_credits: number;
is_valid: boolean;
}
enum FetchState {
EMPTY = "EMPTY",
LOADING = "LOADING",
INVALID = "INVALID",
VALID = "VALID",
}
function AccessCodeSection({ settings, setSettings }: Props) { function AccessCodeSection({ settings, setSettings }: Props) {
const [isLoading, setIsLoading] = useState(false);
const [isValid, setIsValid] = useState(false);
const [usedCredits, setUsedCredits] = useState(0);
const [totalCredits, setTotalCredits] = useState(0);
const throttledAccessCode = useThrottle(settings.accessCode || "", 500);
const fetchState = (() => {
if (!settings.accessCode) return FetchState.EMPTY;
if (isLoading) return FetchState.LOADING;
if (!isValid) return FetchState.INVALID;
return FetchState.VALID;
})();
async function fetchUsage(accessCode: string) {
const res = await fetch(
"https://backend.buildpicoapps.com/screenshot_to_code/get_access_code_usage",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
access_code: accessCode,
secret: PICO_BACKEND_FORM_SECRET,
}),
}
);
const usage = (await res.json()) as UsageResponse;
if (!usage.is_valid) {
setIsValid(false);
} else {
setIsValid(true);
setUsedCredits(usage.used_credits);
setTotalCredits(usage.total_credits);
}
setIsLoading(false);
}
useEffect(() => {
// Don't do anything if access code is empty
if (!throttledAccessCode) return;
setIsLoading(true);
setIsValid(true);
// Wait for 500 ms before fetching usage
setTimeout(async () => {
await fetchUsage(throttledAccessCode);
}, 500);
}, [throttledAccessCode]);
return ( return (
<div className="flex flex-col space-y-4 bg-slate-200 p-4 rounded dark:text-white dark:bg-slate-800"> <div className="flex flex-col space-y-4 bg-slate-200 p-4 rounded dark:text-white dark:bg-slate-800">
<Label htmlFor="access-code"> <Label htmlFor="access-code">
<div>Access Code</div> <div>Access Code</div>
<div className="font-light mt-1 leading-relaxed">
Buy an access code.
</div>
</Label> </Label>
<Input <Input
@ -29,6 +97,44 @@ function AccessCodeSection({ settings, setSettings }: Props) {
})) }))
} }
/> />
{fetchState === "EMPTY" && (
<div className="flex items-center justify-between">
<a href="https://buy.stripe.com/8wM6sre70gBW1nqaEE" target="_blank">
<Button size="sm" variant="secondary">
Buy credits
</Button>
</a>
</div>
)}
{fetchState === "LOADING" && (
<div className="flex items-center justify-between">
<span className="text-xs text-gray-700">Loading...</span>
</div>
)}
{fetchState === "INVALID" && (
<>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-700">Invalid access code</span>
</div>
</>
)}
{fetchState === "VALID" && (
<>
<Progress value={(usedCredits / totalCredits) * 100} />
<div className="flex items-center justify-between">
<span className="text-xs text-gray-700">
{usedCredits} out of {totalCredits} credits used
</span>
<a href="https://buy.stripe.com/8wM6sre70gBW1nqaEE" target="_blank">
<Button size="sm">Add credits</Button>
</a>
</div>
</>
)}
</div> </div>
); );
} }

View File

@ -0,0 +1,26 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@ -869,6 +869,15 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.2" "@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-progress@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.0.3.tgz#8380272fdc64f15cbf263a294dea70a7d5d9b4fa"
integrity sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-roving-focus@1.0.4": "@radix-ui/react-roving-focus@1.0.4":
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"