v0.2.0:24 — Per-entitlement "hide on buy page" toggle
Decouples "what the license grants" from "what the buyer sees on the tier card." Operator can mark individual entitlements as hidden from the buy page tier-card display; the issued license still carries them. Enables the "Everything in Creator, plus:" marketing pattern without duplicating implied entitlements on higher-tier cards. - entitlementBubblePicker accepts a third `initialHidden` param and exposes a `readHidden()` method alongside `read()`. Each granted chip gets a small eye toggle (👁 visible / 👁🗨 hidden). Click chip name = grant/revoke. Click eye = hide-on-buy toggle. De-selecting a chip clears its hidden state automatically. - New per-policy metadata: hidden_entitlements: string[]. Buy page filters before rendering tier-card entitlement chips. Public /v1/products/<slug>/policies exposes the array so SDKs and dynamic pricing pages stay in sync. - Admin Policies grid still shows ALL entitlements (operator-truth view) but hidden ones get muted opacity + strikethrough + a small "(hidden on buy)" italic hint. No schema change; pure metadata pass-through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1156,15 +1156,31 @@ fn render_tier_picker(
|
||||
.unwrap_or_default();
|
||||
// raw slug if the catalog is empty or the slug isn't in
|
||||
// it (legacy slugs that predate the catalog land here).
|
||||
let entitlements_html = if p.entitlements.is_empty() {
|
||||
// Operator-controlled hide list: entitlements the license
|
||||
// grants but the operator doesn't want rendered on the buy
|
||||
// page (e.g. when a higher tier card uses "Everything in
|
||||
// Creator, plus:" marketing copy and doesn't need to repeat
|
||||
// already-implied entitlements). The entitlements still
|
||||
// appear on the issued license — this only filters display.
|
||||
let hidden_on_buy: std::collections::HashSet<&str> = p
|
||||
.metadata
|
||||
.get("hidden_entitlements")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
|
||||
.unwrap_or_default();
|
||||
let visible_entitlements: Vec<&String> = p
|
||||
.entitlements
|
||||
.iter()
|
||||
.filter(|s| !hidden_on_buy.contains(s.as_str()))
|
||||
.collect();
|
||||
let entitlements_html = if visible_entitlements.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let catalog = product.entitlements_catalog.as_deref().unwrap_or(&[]);
|
||||
let lis: Vec<String> = p
|
||||
.entitlements
|
||||
let lis: Vec<String> = visible_entitlements
|
||||
.iter()
|
||||
.map(|slug| {
|
||||
let entry = catalog.iter().find(|e| &e.slug == slug);
|
||||
let entry = catalog.iter().find(|e| &e.slug == *slug);
|
||||
let display = entry
|
||||
.map(|e| if e.name.trim().is_empty() { e.slug.as_str() } else { e.name.as_str() })
|
||||
.unwrap_or(slug.as_str());
|
||||
|
||||
@@ -834,6 +834,15 @@ pub async fn list_public_policies(
|
||||
Some("below") => "below",
|
||||
_ => "above",
|
||||
};
|
||||
// Entitlement slugs the operator chose to hide from the
|
||||
// buy-page tier-card display. The license still grants
|
||||
// these — this only filters what buyers see. SDKs that
|
||||
// render dynamic pricing pages should also filter on this.
|
||||
let hidden_entitlements = p
|
||||
.metadata
|
||||
.get("hidden_entitlements")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| json!([]));
|
||||
let price_sats = p.price_sats_override.unwrap_or(product.price_sats);
|
||||
// Featured discount (if any) — compute the post-discount
|
||||
// price the buyer would actually pay if they bought right
|
||||
@@ -871,6 +880,7 @@ pub async fn list_public_policies(
|
||||
"entitlements": p.entitlements,
|
||||
"marketing_bullets": marketing_bullets,
|
||||
"marketing_bullets_position": marketing_bullets_position,
|
||||
"hidden_entitlements": hidden_entitlements,
|
||||
"highlighted": highlighted,
|
||||
// Recurring-subscription cadence — buy page renders
|
||||
// "Renews every N days" / "$X/month" when is_recurring=true.
|
||||
|
||||
Reference in New Issue
Block a user