RegistryUI recipes
Feature gate
Content shown only when the active organization's plan includes a feature, read from the session's features claim and updated when the session refreshes.
Both sides call useFeatureGate from better-supabase/blocks/entitlements/react; the entitlements block must be installed (npx better-supabase add entitlements). Its page on bettersupabase.com is /recipes/feature-gate.
Web, with shadcn components:
npx shadcn add button skeleton"use client";
import type { ReactNode } from "react";
import { useFeatureGate } from "better-supabase/blocks/entitlements/react";
import { LockIcon } from "lucide-react";
import { useExtracted } from "next-intl";
import type { Entitlement } from "@/lib/claims";
import { buttonVariants } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/navigation";
/**
* Renders `children` only when the active organization's plan includes
* `feature`, read from the session's `features` claim; otherwise a link to the
* plans. UX only: RLS and the route behind it check the feature too.
*/
export function FeatureGate({
feature,
children,
}: {
feature: Entitlement;
children: ReactNode;
}) {
const t = useExtracted("billing");
const { allowed } = useFeatureGate(feature);
if (allowed === undefined) {
return <Skeleton className="h-8 w-28" aria-busy="true" />;
}
if (allowed) {
return children;
}
return (
<Link
href="/settings/billing"
className={buttonVariants({ variant: "outline", size: "sm" })}
data-testid={`upgrade-${feature}`}
>
<LockIcon />
{t("Upgrade to unlock")}
</Link>
);
}React Native, with ActivityIndicator, Text, View from react-native:
import type { ReactNode } from "react";
import { useFeatureGate } from "better-supabase/blocks/entitlements/react";
import { ActivityIndicator, Text, View } from "react-native";
/**
* Renders `children` only when the active organization's plan includes
* `feature`, read from the session's `features` claim. UX only: RLS checks the
* feature too.
*/
export function FeatureGate({
feature,
children,
}: {
readonly feature: string;
readonly children: ReactNode;
}) {
const { allowed } = useFeatureGate(feature);
if (allowed === undefined) {
return <ActivityIndicator />;
}
if (allowed) {
return children;
}
return (
<View style={{ padding: 12, borderWidth: 1, borderRadius: 12 }}>
<Text style={{ fontWeight: "600" }}>Not in your plan</Text>
<Text style={{ color: "#737373" }}>
Upgrade to a plan with {feature} to use this.
</Text>
</View>
);
}Last updated on