Welcome to the Grove project! In this tutorial series, we are going to explore how we handle complex user consents, terms of service updates, and privacy controls.
We start with the most visible part of the system: the Grove Policy Dialog.
Imagine you run a club (your application). One day, the laws change, or you change your house rules. You legally cannot let people into the club until they sign the new agreement.
However, sometimes you want to be nice. You want to tell regular customers: "Hey, new rules are coming next week. You can sign now, or you can come in today but you'll have to sign later."
We need a coded solution that acts like a Bouncer:
This is exactly what the Grove Policy Dialog does.
Before looking at code, let's understand the three pillars of this component.
The dialog doesn't just show up randomly. When the component mounts, it immediately calls the backend to ask: "Does this specific user need to see a notice?" If the answer is "No," the component renders nothing (null) and lets the user pass.
We don't just ask for a signature; we also ask for data privacy permissions (e.g., "Help improve Claude"). The dialog handles bundling the Terms Acceptance with the Data Opt-in preference in a single click.
The GroveDialog is designed to be dropped into high-level views, like an Onboarding flow or a Settings modal.
Here is how you would use the component in your application layout.
import { GroveDialog } from './Grove';
function App() {
return (
<GroveDialog
location="onboarding"
showIfAlreadyViewed={false}
onDone={(decision) => {
console.log("User finished via:", decision);
}}
/>
);
}
location: Tells the analytics system where this is happening (e.g., 'settings').showIfAlreadyViewed: Useful for debugging or settings screens where you want to show the terms even if the user already signed them.onDone: A callback function that runs when the user makes a choice or if the dialog decides not to show itself.
When onDone is called, it returns a GroveDecision string:
'accept_opt_in': User accepted terms AND enabled data training.'accept_opt_out': User accepted terms but disabled data training.'defer': User clicked "Not now" (Grace period only).'skip_rendering': The dialog decided it didn't need to show up.Let's peek under the hood to see how the "Bouncer" does its job.
When the component loads, it performs a "handshake" with the configuration system.
Let's look at the implementation in Grove.tsx. We will break it down into small, digestible pieces.
When the component mounts, it fetches data. It relies on logic we will cover in Consent Decision Logic.
// Inside GroveDialog component
useEffect(() => {
async function checkGroveSettings() {
// 1. Fetch current user settings and the global config
const [settings, config] = await Promise.all([
getGroveSettings(),
getGroveNoticeConfig()
]);
setGroveConfig(config.data); // Save config for later
// 2. logic to decide if we show the dialog
const shouldShow = calculateShouldShowGrove(settings, config, showIfAlreadyViewed);
setShouldShowDialog(shouldShow);
}
checkGroveSettings();
}, []);
Explanation: The useEffect triggers immediately. It waits for the API. If shouldShow is false, the component will eventually return null.
When the user selects an option (like "Accept"), we handle it in onChange.
const onChange = async (value: GroveDecision) => {
// If user accepted with opt-in
if (value === 'accept_opt_in') {
await updateGroveSettings(true); // Send 'true' to API
}
// If user accepted with opt-out
if (value === 'accept_opt_out') {
await updateGroveSettings(false); // Send 'false' to API
}
// Tell parent component we are done
onDone(value);
};
Explanation: This maps the specific UI button the user clicked to an API call (updateGroveSettings). This ensures the user's preference is saved immediately.
The dialog needs to know what to say. The content changes based on whether we are in a "Grace Period" or not. We will detail exactly how these strategies work in Policy Phase Content Strategies.
// Inside the render return
<Box flexDirection="column">
{groveConfig?.notice_is_grace_period ? (
<GracePeriodContentBody /> // "Update coming soon..."
) : (
<PostGracePeriodContentBody /> // "Update is here."
)}
</Box>
Explanation: We simply check notice_is_grace_period from the config we fetched earlier. This switches the text from "An update will take effect on..." to "We've updated our terms...".
If we are in a Grace Period, we render an extra button to let the user "Defer".
// Creating the list of buttons
const deferOption = groveConfig?.notice_is_grace_period
? [{ label: "Not now", value: "defer" }] // Add "Not now" button
: []; // No extra button
// Combine with accept options
const allOptions = [...acceptOptions, ...deferOption];
Explanation: If it's a grace period, we inject the "Not now" option into the dropdown or button list. If it's mandatory, that list is empty, forcing the user to pick one of the "Accept" options.
There is a secondary component in this file called PrivacySettingsDialog. While the GroveDialog is for interrupting the user, the PrivacySettingsDialog is for when the user wants to change their mind later.
We will explore the details of this interface in Privacy Settings Interface.
You have learned about the Grove Policy Dialog, the "Bouncer" of our application.
In the next chapter, we will look at exactly what text and content we show inside this dialog to maximize clarity and trust.
Next Chapter: Policy Phase Content Strategies
Generated by Code IQ