chore: add pricing plan lock for machine types
This commit is contained in:
parent
42aaf1acb9
commit
9de266fbab
@ -1,5 +1,6 @@
|
|||||||
import { AccessType } from "../../../lib/AccessType";
|
import { AccessType } from "../../../lib/AccessType";
|
||||||
import { MachineList } from "@/components/MachineList";
|
import { MachineList } from "@/components/MachineList";
|
||||||
|
import { SubscriptionProvider } from "@/components/useCurrentPlan";
|
||||||
import { db } from "@/db/db";
|
import { db } from "@/db/db";
|
||||||
import { machinesTable } from "@/db/schema";
|
import { machinesTable } from "@/db/schema";
|
||||||
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
@ -32,12 +33,12 @@ async function MachineListServer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{/* <div>Machines</div> */}
|
<SubscriptionProvider sub={sub}>
|
||||||
<MachineList
|
<MachineList
|
||||||
sub={sub}
|
data={machines}
|
||||||
data={machines}
|
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
|
||||||
userMetadata={AccessType.parse(user.privateMetadata ?? {})}
|
/>
|
||||||
/>
|
</SubscriptionProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
24
web/src/components/CurrentPlanContextType.tsx
Normal file
24
web/src/components/CurrentPlanContextType.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
|
import * as React from "react";
|
||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type CurrentPlanContextType = Awaited<
|
||||||
|
ReturnType<typeof getCurrentPlanWithAuth>
|
||||||
|
>;
|
||||||
|
export const CurrentPlanContext = createContext<
|
||||||
|
CurrentPlanContextType | undefined
|
||||||
|
>(undefined);
|
||||||
|
export function SubscriptionProvider({
|
||||||
|
sub,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
sub: CurrentPlanContextType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CurrentPlanContext.Provider value={sub}>
|
||||||
|
{children}
|
||||||
|
</CurrentPlanContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
@ -183,6 +183,7 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const machine = row.original;
|
const machine = row.original;
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const sub = useCurrentPlan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -291,7 +292,10 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
fieldType: "models",
|
fieldType: "models",
|
||||||
},
|
},
|
||||||
gpu: {
|
gpu: {
|
||||||
inputProps: {},
|
fieldType: "gpuPicker",
|
||||||
|
inputProps: {
|
||||||
|
sub: sub,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -319,14 +323,14 @@ export const columns: ColumnDef<Machine>[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
import { useCurrentPlan } from "./useCurrentPlan";
|
||||||
|
|
||||||
export function MachineList({
|
export function MachineList({
|
||||||
data,
|
data,
|
||||||
userMetadata,
|
userMetadata,
|
||||||
sub,
|
|
||||||
}: {
|
}: {
|
||||||
data: Machine[];
|
data: Machine[];
|
||||||
userMetadata: z.infer<typeof AccessType>;
|
userMetadata: z.infer<typeof AccessType>;
|
||||||
sub: Awaited<ReturnType<typeof getCurrentPlanWithAuth>>;
|
|
||||||
}) {
|
}) {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
@ -336,6 +340,8 @@ export function MachineList({
|
|||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
|
||||||
|
const sub = useCurrentPlan();
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
@ -420,11 +426,9 @@ export function MachineList({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
gpu: {
|
gpu: {
|
||||||
fieldType: !userMetadata.betaFeaturesAccess
|
fieldType: "gpuPicker",
|
||||||
? "fallback"
|
|
||||||
: "select",
|
|
||||||
inputProps: {
|
inputProps: {
|
||||||
disabled: !userMetadata.betaFeaturesAccess,
|
sub: sub,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
90
web/src/components/custom-form/gpu-picker.tsx
Normal file
90
web/src/components/custom-form/gpu-picker.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { AutoFormInputComponentProps } from "@/components/ui/auto-form/types";
|
||||||
|
import { getBaseSchema } from "@/components/ui/auto-form/utils";
|
||||||
|
import {
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Lock } from "lucide-react";
|
||||||
|
import * as z from "zod";
|
||||||
|
|
||||||
|
export default function AutoFormGPUPicker({
|
||||||
|
label,
|
||||||
|
isRequired,
|
||||||
|
field,
|
||||||
|
fieldConfigItem,
|
||||||
|
zodItem,
|
||||||
|
}: AutoFormInputComponentProps) {
|
||||||
|
const baseValues = (getBaseSchema(zodItem) as unknown as z.ZodEnum<any>)._def
|
||||||
|
.values;
|
||||||
|
|
||||||
|
let values: [string, string][] = [];
|
||||||
|
if (!Array.isArray(baseValues)) {
|
||||||
|
values = Object.entries(baseValues);
|
||||||
|
} else {
|
||||||
|
values = baseValues.map((value) => [value, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findItem(value: any) {
|
||||||
|
return values.find((item) => item[0] === value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = fieldConfigItem.inputProps?.sub?.plan;
|
||||||
|
const enabledGPU = ["T4"];
|
||||||
|
|
||||||
|
if (plan == "pro") {
|
||||||
|
enabledGPU.push("A10G");
|
||||||
|
} else if (plan == "enterprise") {
|
||||||
|
enabledGPU.push("A10G");
|
||||||
|
enabledGPU.push("A100");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{label}
|
||||||
|
{isRequired && <span className="text-destructive"> *</span>}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue
|
||||||
|
className="w-full"
|
||||||
|
placeholder={fieldConfigItem.inputProps?.placeholder}
|
||||||
|
>
|
||||||
|
{field.value ? findItem(field.value)?.[1] : "Select an option"}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{values.map(([value, label]) => {
|
||||||
|
const enabled = enabledGPU.includes(value);
|
||||||
|
return (
|
||||||
|
<SelectItem value={label} key={value} disabled={!enabled}>
|
||||||
|
{label}
|
||||||
|
{!enabled && (
|
||||||
|
<span className="mx-2 inline-flex items-center justify-center gap-2">
|
||||||
|
Upgrade to enabled this options <Lock size={14}></Lock>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{fieldConfigItem.description && (
|
||||||
|
<FormDescription>{fieldConfigItem.description}</FormDescription>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import AutoFormGPUPicker from "@/components/custom-form/gpu-picker";
|
||||||
import AutoFormCheckbox from "./fields/checkbox";
|
import AutoFormCheckbox from "./fields/checkbox";
|
||||||
import AutoFormDate from "./fields/date";
|
import AutoFormDate from "./fields/date";
|
||||||
import AutoFormEnum from "./fields/enum";
|
import AutoFormEnum from "./fields/enum";
|
||||||
@ -22,6 +23,7 @@ export const INPUT_COMPONENTS = {
|
|||||||
// Customs
|
// Customs
|
||||||
snapshot: AutoFormSnapshotPicker,
|
snapshot: AutoFormSnapshotPicker,
|
||||||
models: AutoFormModelsPicker,
|
models: AutoFormModelsPicker,
|
||||||
|
gpuPicker: AutoFormGPUPicker,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
import type { INPUT_COMPONENTS } from "./config";
|
import type { INPUT_COMPONENTS } from "./config";
|
||||||
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
|
||||||
import type * as z from "zod";
|
import type * as z from "zod";
|
||||||
@ -6,6 +7,7 @@ export type FieldConfigItem = {
|
|||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
|
inputProps?: React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
|
sub?: Awaited<ReturnType<typeof getCurrentPlanWithAuth>>;
|
||||||
};
|
};
|
||||||
fieldType?:
|
fieldType?:
|
||||||
| keyof typeof INPUT_COMPONENTS
|
| keyof typeof INPUT_COMPONENTS
|
||||||
|
36
web/src/components/useCurrentPlan.tsx
Normal file
36
web/src/components/useCurrentPlan.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getCurrentPlanWithAuth } from "@/server/getCurrentPlan";
|
||||||
|
import * as React from "react";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
type CurrentPlanContextType = Awaited<
|
||||||
|
ReturnType<typeof getCurrentPlanWithAuth>
|
||||||
|
>;
|
||||||
|
const CurrentPlanContext = createContext<CurrentPlanContextType | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function SubscriptionProvider({
|
||||||
|
sub,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
sub: CurrentPlanContextType;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CurrentPlanContext.Provider value={sub}>
|
||||||
|
{children}
|
||||||
|
</CurrentPlanContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCurrentPlan = (): CurrentPlanContextType => {
|
||||||
|
const context = useContext(CurrentPlanContext);
|
||||||
|
|
||||||
|
// if (context === undefined) {
|
||||||
|
// throw new Error("useCurrentPlan must be used within a CurrentPlanProvider");
|
||||||
|
// }
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
@ -3,6 +3,7 @@ import { and, desc, eq, isNull, or } from "drizzle-orm";
|
|||||||
import { subscriptionStatusTable } from "@/db/schema";
|
import { subscriptionStatusTable } from "@/db/schema";
|
||||||
import { APIKeyUserType } from "@/server/APIKeyBodyRequest";
|
import { APIKeyUserType } from "@/server/APIKeyBodyRequest";
|
||||||
import { auth } from "@clerk/nextjs";
|
import { auth } from "@clerk/nextjs";
|
||||||
|
import "server-only";
|
||||||
|
|
||||||
export async function getCurrentPlanWithAuth() {
|
export async function getCurrentPlanWithAuth() {
|
||||||
const { userId, orgId } = auth();
|
const { userId, orgId } = auth();
|
||||||
@ -23,7 +24,10 @@ export async function getCurrentPlan({ user_id, org_id }: APIKeyUserType) {
|
|||||||
eq(subscriptionStatusTable.user_id, user_id),
|
eq(subscriptionStatusTable.user_id, user_id),
|
||||||
org_id
|
org_id
|
||||||
? eq(subscriptionStatusTable.org_id, org_id)
|
? eq(subscriptionStatusTable.org_id, org_id)
|
||||||
: or(isNull(subscriptionStatusTable.org_id), eq(subscriptionStatusTable.org_id, "")),
|
: or(
|
||||||
|
isNull(subscriptionStatusTable.org_id),
|
||||||
|
eq(subscriptionStatusTable.org_id, ""),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
orderBy: desc(subscriptionStatusTable.created_at),
|
orderBy: desc(subscriptionStatusTable.created_at),
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user