Billing UI (#386)
This commit is contained in:
63
skyvern-frontend/src/components/ui/alert.tsx
Normal file
63
skyvern-frontend/src/components/ui/alert.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive border-destructive/50 text-destructive-foreground dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
success:
|
||||||
|
"bg-success border-success/50 text-success-foreground dark:border-success [&>svg]:text-success",
|
||||||
|
warning:
|
||||||
|
"bg-warning border-warning/50 text-warning-foreground dark:border-warning [&>svg]:text-warning",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Alert.displayName = "Alert";
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertTitle.displayName = "AlertTitle";
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDescription.displayName = "AlertDescription";
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
@@ -30,6 +30,10 @@ const toastVariants = cva(
|
|||||||
default: "border bg-background text-foreground",
|
default: "border bg-background text-foreground",
|
||||||
destructive:
|
destructive:
|
||||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||||
|
success:
|
||||||
|
"success group border-success bg-success text-success-foreground",
|
||||||
|
warning:
|
||||||
|
"warning group border-warning bg-warning text-warning-foreground",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -57,6 +57,12 @@
|
|||||||
--accent: 217.2 32.6% 17.5%;
|
--accent: 217.2 32.6% 17.5%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
--warning: 40.6, 96.1%, 40.4%;
|
||||||
|
--warning-foreground: 47.9, 95.8%, 53.1%;
|
||||||
|
|
||||||
|
--success: 142.1, 76.2%, 36.3%;
|
||||||
|
--success-foreground: 141.9, 69.2%, 58%;
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function Settings() {
|
|||||||
useSettingsStore();
|
useSettingsStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8 max-w-6xl mx-auto p-8 pt-0">
|
<div className="flex flex-col gap-8">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="border-b-2">
|
<CardHeader className="border-b-2">
|
||||||
<CardTitle className="text-lg">Settings</CardTitle>
|
<CardTitle className="text-lg">Settings</CardTitle>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
|
|||||||
|
|
||||||
function SettingsPageLayout() {
|
function SettingsPageLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 px-6">
|
<div className="max-w-6xl mx-auto px-8">
|
||||||
<main>
|
<main>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
|
|||||||
|
|
||||||
function TasksPageLayout() {
|
function TasksPageLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="px-6 flex grow flex-col gap-4">
|
<div className="max-w-6xl mx-auto px-8">
|
||||||
<main>
|
<main>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import fetchToCurl from "fetch-to-curl";
|
|||||||
import { apiBaseUrl } from "@/util/env";
|
import { apiBaseUrl } from "@/util/env";
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { useApiCredential } from "@/hooks/useApiCredential";
|
import { useApiCredential } from "@/hooks/useApiCredential";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
const createNewTaskFormSchema = z
|
const createNewTaskFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -111,7 +112,23 @@ function CreateNewTaskForm({ initialValues }: Props) {
|
|||||||
{ data: { task_id: string } }
|
{ data: { task_id: string } }
|
||||||
>("/tasks", taskRequest);
|
>("/tasks", taskRequest);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error: AxiosError) => {
|
||||||
|
if (error.response?.status === 402) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to create task",
|
||||||
|
description:
|
||||||
|
"You don't have enough credits to run this task. Go to billing to see your credit balance.",
|
||||||
|
action: (
|
||||||
|
<ToastAction altText="Go to Billing">
|
||||||
|
<Button asChild>
|
||||||
|
<Link to="billing">Go to Billing</Link>
|
||||||
|
</Button>
|
||||||
|
</ToastAction>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "There was an error creating the task.",
|
title: "There was an error creating the task.",
|
||||||
@@ -120,6 +137,7 @@ function CreateNewTaskForm({ initialValues }: Props) {
|
|||||||
},
|
},
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
toast({
|
toast({
|
||||||
|
variant: "success",
|
||||||
title: "Task Created",
|
title: "Task Created",
|
||||||
description: `${response.data.task_id} created successfully.`,
|
description: `${response.data.task_id} created successfully.`,
|
||||||
action: (
|
action: (
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function TaskTemplates() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-8">
|
<div>
|
||||||
<section className="py-4">
|
<section className="py-4">
|
||||||
<header>
|
<header>
|
||||||
<h1 className="text-3xl">Skyvern Templates</h1>
|
<h1 className="text-3xl">Skyvern Templates</h1>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function TaskDetails() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8 max-w-6xl mx-auto p-8 pt-0">
|
<div className="flex flex-col gap-8">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Label className="w-32 shrink-0 text-lg">Task ID</Label>
|
<Label className="w-32 shrink-0 text-lg">Task ID</Label>
|
||||||
<Input value={taskId} readOnly />
|
<Input value={taskId} readOnly />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { TaskHistory } from "./TaskHistory";
|
|||||||
|
|
||||||
function TaskList() {
|
function TaskList() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8 max-w-6xl mx-auto p-8 pt-0">
|
<div className="flex flex-col gap-8">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="border-b-2">
|
<CardHeader className="border-b-2">
|
||||||
<CardTitle className="text-xl">Running Tasks</CardTitle>
|
<CardTitle className="text-xl">Running Tasks</CardTitle>
|
||||||
|
|||||||
Reference in New Issue
Block a user