Properly paywall ui for labels

This commit is contained in:
Owen
2026-06-10 20:32:07 -07:00
parent 1a942937e6
commit cc498f0e33
3 changed files with 29 additions and 7 deletions

View File

@@ -1,8 +1,10 @@
"use client"; "use client";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -18,6 +20,7 @@ import {
CredenzaTitle CredenzaTitle
} from "./Credenza"; } from "./Credenza";
import { OrgLabelForm } from "./OrgLabelForm"; import { OrgLabelForm } from "./OrgLabelForm";
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export type CreateOrgLabelDialogProps = { export type CreateOrgLabelDialogProps = {
@@ -35,6 +38,8 @@ export function CreateOrgLabelDialog({
}: CreateOrgLabelDialogProps) { }: CreateOrgLabelDialogProps) {
const t = useTranslations(); const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const { isPaidUser } = usePaidStatus();
const canManageLabels = isPaidUser(tierMatrix.labels);
const [isSubmitting, startTransition] = useTransition(); const [isSubmitting, startTransition] = useTransition();
async function createOrgLabel(data: { name: string; color: string }) { async function createOrgLabel(data: { name: string; color: string }) {
@@ -79,8 +84,11 @@ export function CreateOrgLabelDialog({
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
<PaidFeaturesAlert tiers={tierMatrix.labels} />
<OrgLabelForm <OrgLabelForm
disabled={!canManageLabels}
onSubmit={(data) => { onSubmit={(data) => {
if (!canManageLabels) return;
startTransition(async () => createOrgLabel(data)); startTransition(async () => createOrgLabel(data));
}} }}
/> />
@@ -98,7 +106,7 @@ export function CreateOrgLabelDialog({
<Button <Button
type="submit" type="submit"
form="org-label-form" form="org-label-form"
disabled={isSubmitting} disabled={isSubmitting || !canManageLabels}
loading={isSubmitting} loading={isSubmitting}
> >
{t("labelCreate")} {t("labelCreate")}

View File

@@ -1,8 +1,10 @@
"use client"; "use client";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -18,6 +20,7 @@ import {
CredenzaTitle CredenzaTitle
} from "./Credenza"; } from "./Credenza";
import { OrgLabelForm } from "./OrgLabelForm"; import { OrgLabelForm } from "./OrgLabelForm";
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export type EditOrgLabelDialogProps = { export type EditOrgLabelDialogProps = {
@@ -41,6 +44,8 @@ export function EditOrgLabelDialog({
}: EditOrgLabelDialogProps) { }: EditOrgLabelDialogProps) {
const t = useTranslations(); const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const { isPaidUser } = usePaidStatus();
const canManageLabels = isPaidUser(tierMatrix.labels);
const [isSubmitting, startTransition] = useTransition(); const [isSubmitting, startTransition] = useTransition();
async function editOrgLabel(data: { name: string; color: string }) { async function editOrgLabel(data: { name: string; color: string }) {
@@ -85,9 +90,12 @@ export function EditOrgLabelDialog({
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
<PaidFeaturesAlert tiers={tierMatrix.labels} />
<OrgLabelForm <OrgLabelForm
disabled={!canManageLabels}
defaultValue={label} defaultValue={label}
onSubmit={(data) => { onSubmit={(data) => {
if (!canManageLabels) return;
startTransition(async () => editOrgLabel(data)); startTransition(async () => editOrgLabel(data));
}} }}
/> />
@@ -105,7 +113,7 @@ export function EditOrgLabelDialog({
<Button <Button
type="submit" type="submit"
form="org-label-form" form="org-label-form"
disabled={isSubmitting} disabled={isSubmitting || !canManageLabels}
loading={isSubmitting} loading={isSubmitting}
> >
{t("labelEdit")} {t("labelEdit")}

View File

@@ -35,9 +35,14 @@ export type LabelFormData = z.infer<typeof labelFormSchema>;
export type OrgLabelFormProps = { export type OrgLabelFormProps = {
onSubmit: (data: LabelFormData) => void; onSubmit: (data: LabelFormData) => void;
defaultValue?: LabelFormData; defaultValue?: LabelFormData;
disabled?: boolean;
}; };
export function OrgLabelForm({ onSubmit, defaultValue }: OrgLabelFormProps) { export function OrgLabelForm({
onSubmit,
defaultValue,
disabled = false
}: OrgLabelFormProps) {
const t = useTranslations(); const t = useTranslations();
const colorValues = Object.values(LABEL_COLORS); const colorValues = Object.values(LABEL_COLORS);
@@ -70,9 +75,7 @@ export function OrgLabelForm({ onSubmit, defaultValue }: OrgLabelFormProps) {
<FormItem> <FormItem>
<FormLabel>{t("labelNameField")}</FormLabel> <FormLabel>{t("labelNameField")}</FormLabel>
<FormControl> <FormControl>
<Input <Input {...field} disabled={disabled} />
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -88,6 +91,7 @@ export function OrgLabelForm({ onSubmit, defaultValue }: OrgLabelFormProps) {
<Select <Select
onValueChange={field.onChange} onValueChange={field.onChange}
value={field.value} value={field.value}
disabled={disabled}
> >
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue <SelectValue
@@ -110,7 +114,9 @@ export function OrgLabelForm({ onSubmit, defaultValue }: OrgLabelFormProps) {
}} }}
/> />
<span data-name> <span data-name>
{color.charAt(0).toUpperCase() + {color
.charAt(0)
.toUpperCase() +
color.slice(1)} color.slice(1)}
</span> </span>
</SelectItem> </SelectItem>