fix: standardize locked-vs-hidden treatment across all 9 per-tier gated features (v0.79.0)
Rule, applied consistently everywhere via a new isFeatureAvailableAnyTier() helper: if a feature is enabled on at least one tier, it stays visible for locked-out viewers with a small "Pro" badge and opens an upgrade prompt on click; if a feature is disabled on every tier, it hides entirely, since there's no upgrade path to point at. Covers: recipe variations, meal/drink pairings, nutrition estimation, Markdown export (5 call sites), weekly nutrition, import from URL, import from photo, and the Instacart grocery-delivery menu item. Previously inconsistent — some hid outright, one showed a lock icon overlapping its own icon. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,16 +14,24 @@ import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { GroceryExportPayload } from "@/lib/grocery-export";
|
||||
import { groceryExportToText } from "@/lib/grocery-export";
|
||||
import { UpgradeDialog } from "@/components/premium/upgrade-dialog";
|
||||
import { ProBadge } from "@/components/premium/pro-badge";
|
||||
|
||||
interface Props {
|
||||
listId: string;
|
||||
/** Set when NEXT_PUBLIC_GROCERY_PROVIDER=instacart — otherwise only "copy as text" is offered. */
|
||||
instacartEnabled: boolean;
|
||||
/** Set when NEXT_PUBLIC_GROCERY_PROVIDER=instacart and the feature is
|
||||
* enabled for at least one tier — otherwise only "copy as text" is
|
||||
* offered, since there's no upgrade path to point at. */
|
||||
instacartAvailable: boolean;
|
||||
/** Available on some tier but not the viewer's — the menu item still
|
||||
* shows (with a "Pro" upsell) instead of hiding. */
|
||||
instacartLocked?: boolean;
|
||||
}
|
||||
|
||||
export function GroceryExportButton({ listId, instacartEnabled }: Props) {
|
||||
export function GroceryExportButton({ listId, instacartAvailable, instacartLocked = false }: Props) {
|
||||
const t = useTranslations("shoppingLists");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [upgradeOpen, setUpgradeOpen] = useState(false);
|
||||
|
||||
async function fetchPayload(): Promise<GroceryExportPayload | null> {
|
||||
const res = await fetch(`/api/v1/shopping-lists/${listId}/export`);
|
||||
@@ -80,13 +88,20 @@ export function GroceryExportButton({ listId, instacartEnabled }: Props) {
|
||||
<Copy className="h-4 w-4" />
|
||||
{t("copyAsText")}
|
||||
</DropdownMenuItem>
|
||||
{instacartEnabled && (
|
||||
<DropdownMenuItem onClick={() => void handleInstacart()}>
|
||||
{instacartAvailable && (
|
||||
<DropdownMenuItem onClick={() => (instacartLocked ? setUpgradeOpen(true) : void handleInstacart())}>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
{t("sendToInstacart")}
|
||||
{instacartLocked && <ProBadge className="ml-auto" />}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
<UpgradeDialog
|
||||
open={upgradeOpen}
|
||||
onOpenChange={setUpgradeOpen}
|
||||
featureKey="grocery_delivery"
|
||||
featureLabel="Grocery delivery integration"
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user