feat(billing): defer the cardless trial for invited collaborators

An account that signs up through an invitation works on the inviter's
billing, so handing it a trial at signup spent its only trial before it
owned anything. The trial is now held back for collaborators and claimed
only explicitly: a Start Free Trial button on the new-workspace and
billing screens calls the new POST /api/billing/trial endpoint, which
grants the once-per-account trial atomically. Nothing starts the clock
as a side effect, and pure collaborators no longer see a trial-ending
banner about work that is not theirs.
This commit is contained in:
2026-09-01 15:07:54 +03:00
parent cba8163286
commit 4b3c3934dd
12 changed files with 684 additions and 47 deletions
@@ -15,12 +15,35 @@ export default function NewWorkspacePage({
}: {
workspaceCreation: {
canCreateWorkspace: boolean;
canStartTrial?: boolean;
reason: string | null;
};
}) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [isStartingTrial, setIsStartingTrial] = useState(false);
const [error, setError] = useState('');
const handleStartTrial = async () => {
setIsStartingTrial(true);
setError('');
try {
const response = await fetch('/api/billing/trial', { method: 'POST' });
const data = await response.json();
if (!response.ok) {
setError(data.error || 'Failed to start your free trial');
return;
}
router.refresh();
} catch {
setError('Something went wrong. Please try again.');
} finally {
setIsStartingTrial(false);
}
};
const [formData, setFormData] = useState({
name: '',
description: '',
@@ -76,7 +99,11 @@ export default function NewWorkspacePage({
)}
</div>
<CardTitle className="text-2xl">
{workspaceCreation.canCreateWorkspace ? 'Create New Workspace' : 'Upgrade Required'}
{workspaceCreation.canCreateWorkspace
? 'Create New Workspace'
: workspaceCreation.canStartTrial
? 'Start Your Free Trial'
: 'Upgrade Required'}
</CardTitle>
<CardDescription className="text-base">
{workspaceCreation.canCreateWorkspace
@@ -139,9 +166,27 @@ export default function NewWorkspacePage({
You can still create and manage projects inside workspaces where you are already a
member.
</p>
<Button asChild className="w-full">
<Link href="/settings">Open Billing Settings</Link>
</Button>
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
{workspaceCreation.canStartTrial ? (
<Button className="w-full" onClick={handleStartTrial} disabled={isStartingTrial}>
{isStartingTrial ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Starting Trial...
</>
) : (
'Start Free Trial'
)}
</Button>
) : (
<Button asChild className="w-full">
<Link href="/settings">Open Billing Settings</Link>
</Button>
)}
</div>
)}
</CardContent>