stagent 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState, useCallback } from "react";
|
|
4
|
-
import { Crown, BarChart3, Brain, Calendar, Zap, ExternalLink, TrendingUp, Cloud, Store } from "lucide-react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import {
|
|
7
|
-
Card,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardHeader,
|
|
11
|
-
CardTitle,
|
|
12
|
-
} from "@/components/ui/card";
|
|
13
|
-
import { Button } from "@/components/ui/button";
|
|
14
|
-
import { Badge } from "@/components/ui/badge";
|
|
15
|
-
import { Progress } from "@/components/ui/progress";
|
|
16
|
-
import { TIER_LABELS, TIER_PRICING, type LicenseTier } from "@/lib/license/tier-limits";
|
|
17
|
-
import { ActivationForm } from "./activation-form";
|
|
18
|
-
|
|
19
|
-
interface LicenseStatus {
|
|
20
|
-
tier: LicenseTier;
|
|
21
|
-
status: string;
|
|
22
|
-
email: string | null;
|
|
23
|
-
isPremium: boolean;
|
|
24
|
-
activatedAt: string | null;
|
|
25
|
-
expiresAt: string | null;
|
|
26
|
-
limits: Record<string, number>;
|
|
27
|
-
features: Record<string, boolean>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface UsageData {
|
|
31
|
-
agentMemories: { current: number; limit: number };
|
|
32
|
-
contextVersions: { current: number; limit: number };
|
|
33
|
-
activeSchedules: { current: number; limit: number };
|
|
34
|
-
parallelWorkflows: { current: number; limit: number };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const TIER_BADGE_VARIANT: Record<LicenseTier, "secondary" | "default" | "destructive" | "outline"> = {
|
|
38
|
-
community: "secondary",
|
|
39
|
-
solo: "default",
|
|
40
|
-
operator: "default",
|
|
41
|
-
scale: "outline",
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const USAGE_HINTS: Record<string, { sub: string; atLimit: string }> = {
|
|
45
|
-
agentMemories: {
|
|
46
|
-
sub: "Cumulative per profile. New writes blocked at limit — existing memories preserved.",
|
|
47
|
-
atLimit: "At capacity — archive old memories or upgrade to continue learning.",
|
|
48
|
-
},
|
|
49
|
-
contextVersions: {
|
|
50
|
-
sub: "Cumulative per profile. New proposals blocked at limit — existing versions preserved.",
|
|
51
|
-
atLimit: "At capacity — upgrade to unlock more self-improvement cycles.",
|
|
52
|
-
},
|
|
53
|
-
activeSchedules: {
|
|
54
|
-
sub: "Concurrent active schedules. Pausing a schedule frees a slot.",
|
|
55
|
-
atLimit: "All slots used — pause an existing schedule or upgrade for more.",
|
|
56
|
-
},
|
|
57
|
-
parallelWorkflows: {
|
|
58
|
-
sub: "Concurrent running workflows. Slots free when tasks complete.",
|
|
59
|
-
atLimit: "All slots busy — wait for a task to finish or upgrade.",
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
function UsageCard({
|
|
64
|
-
icon: Icon,
|
|
65
|
-
label,
|
|
66
|
-
current,
|
|
67
|
-
limit,
|
|
68
|
-
resourceKey,
|
|
69
|
-
}: {
|
|
70
|
-
icon: typeof Brain;
|
|
71
|
-
label: string;
|
|
72
|
-
current: number;
|
|
73
|
-
limit: number;
|
|
74
|
-
resourceKey: string;
|
|
75
|
-
}) {
|
|
76
|
-
const isUnlimited = limit === -1;
|
|
77
|
-
const ratio = isUnlimited ? 0 : (current / limit) * 100;
|
|
78
|
-
const isWarning = !isUnlimited && ratio >= 80;
|
|
79
|
-
const isBlocked = !isUnlimited && ratio >= 100;
|
|
80
|
-
const hints = USAGE_HINTS[resourceKey];
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<div className="surface-card-muted rounded-lg p-3 space-y-2">
|
|
84
|
-
<div className="flex items-center justify-between">
|
|
85
|
-
<div className="flex items-center gap-2">
|
|
86
|
-
<Icon className="h-3.5 w-3.5 text-muted-foreground" />
|
|
87
|
-
<span className="text-xs font-medium">{label}</span>
|
|
88
|
-
</div>
|
|
89
|
-
<span className={`text-xs ${isBlocked ? "text-destructive font-medium" : "text-muted-foreground"}`}>
|
|
90
|
-
{current} / {isUnlimited ? "∞" : limit}
|
|
91
|
-
</span>
|
|
92
|
-
</div>
|
|
93
|
-
{!isUnlimited && (
|
|
94
|
-
<Progress
|
|
95
|
-
value={Math.min(ratio, 100)}
|
|
96
|
-
className={`h-1.5 ${isBlocked ? "[&>div]:bg-destructive" : isWarning ? "[&>div]:bg-amber-500" : ""}`}
|
|
97
|
-
/>
|
|
98
|
-
)}
|
|
99
|
-
{hints && (
|
|
100
|
-
<p className="text-[10px] leading-relaxed text-muted-foreground">
|
|
101
|
-
{isBlocked ? hints.atLimit : hints.sub}
|
|
102
|
-
</p>
|
|
103
|
-
)}
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function TierBenefit({ icon: Icon, text }: { icon: typeof Brain; text: string }) {
|
|
109
|
-
return (
|
|
110
|
-
<li className="flex items-center gap-2">
|
|
111
|
-
<Icon className="h-3 w-3 text-primary shrink-0" />
|
|
112
|
-
<span className="text-[11px] text-muted-foreground">{text}</span>
|
|
113
|
-
</li>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function SubscriptionSection() {
|
|
118
|
-
const [license, setLicense] = useState<LicenseStatus | null>(null);
|
|
119
|
-
const [usage, setUsage] = useState<UsageData | null>(null);
|
|
120
|
-
const [loading, setLoading] = useState(true);
|
|
121
|
-
const [checkoutLoading, setCheckoutLoading] = useState(false);
|
|
122
|
-
const [billingPeriod, setBillingPeriod] = useState<"monthly" | "annual">("monthly");
|
|
123
|
-
|
|
124
|
-
const fetchStatus = useCallback(async () => {
|
|
125
|
-
try {
|
|
126
|
-
const [licRes, usageRes] = await Promise.all([
|
|
127
|
-
fetch("/api/license"),
|
|
128
|
-
fetch("/api/license/usage"),
|
|
129
|
-
]);
|
|
130
|
-
if (licRes.ok) setLicense(await licRes.json());
|
|
131
|
-
if (usageRes.ok) setUsage(await usageRes.json());
|
|
132
|
-
} catch {
|
|
133
|
-
// Fail silently
|
|
134
|
-
} finally {
|
|
135
|
-
setLoading(false);
|
|
136
|
-
}
|
|
137
|
-
}, []);
|
|
138
|
-
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
fetchStatus();
|
|
141
|
-
|
|
142
|
-
// Check for post-purchase success
|
|
143
|
-
const params = new URLSearchParams(window.location.search);
|
|
144
|
-
if (params.get("success") === "true") {
|
|
145
|
-
toast.success("Subscription activated! Premium features are now available.");
|
|
146
|
-
// Clean up URL
|
|
147
|
-
const url = new URL(window.location.href);
|
|
148
|
-
url.searchParams.delete("success");
|
|
149
|
-
window.history.replaceState({}, "", url.toString());
|
|
150
|
-
// Refresh status after a short delay to pick up the new tier
|
|
151
|
-
setTimeout(fetchStatus, 2000);
|
|
152
|
-
}
|
|
153
|
-
}, [fetchStatus]);
|
|
154
|
-
|
|
155
|
-
if (loading) {
|
|
156
|
-
return (
|
|
157
|
-
<Card>
|
|
158
|
-
<CardHeader>
|
|
159
|
-
<CardTitle className="flex items-center gap-2">
|
|
160
|
-
<Crown className="h-5 w-5" />
|
|
161
|
-
Subscription
|
|
162
|
-
</CardTitle>
|
|
163
|
-
</CardHeader>
|
|
164
|
-
<CardContent>
|
|
165
|
-
<div className="animate-pulse space-y-3">
|
|
166
|
-
<div className="h-4 bg-muted rounded w-1/3" />
|
|
167
|
-
<div className="h-20 bg-muted rounded" />
|
|
168
|
-
</div>
|
|
169
|
-
</CardContent>
|
|
170
|
-
</Card>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const tier = license?.tier ?? "community";
|
|
175
|
-
const isPremium = license?.isPremium ?? false;
|
|
176
|
-
|
|
177
|
-
async function handleUpgrade(targetTier: string) {
|
|
178
|
-
setCheckoutLoading(true);
|
|
179
|
-
try {
|
|
180
|
-
const res = await fetch("/api/license/checkout", {
|
|
181
|
-
method: "POST",
|
|
182
|
-
headers: { "Content-Type": "application/json" },
|
|
183
|
-
body: JSON.stringify({ tier: targetTier, billingPeriod }),
|
|
184
|
-
});
|
|
185
|
-
const data = await res.json();
|
|
186
|
-
if (data.url) {
|
|
187
|
-
window.location.href = data.url;
|
|
188
|
-
} else {
|
|
189
|
-
toast.error(data.error ?? "Failed to start checkout");
|
|
190
|
-
}
|
|
191
|
-
} catch {
|
|
192
|
-
toast.error("Failed to connect to billing service");
|
|
193
|
-
} finally {
|
|
194
|
-
setCheckoutLoading(false);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
<Card>
|
|
200
|
-
<CardHeader>
|
|
201
|
-
<CardTitle className="flex items-center gap-2">
|
|
202
|
-
<Crown className="h-5 w-5" />
|
|
203
|
-
Subscription
|
|
204
|
-
</CardTitle>
|
|
205
|
-
<CardDescription>
|
|
206
|
-
Manage your plan and usage limits
|
|
207
|
-
</CardDescription>
|
|
208
|
-
</CardHeader>
|
|
209
|
-
<CardContent className="space-y-6">
|
|
210
|
-
{/* Current Plan */}
|
|
211
|
-
<div className="flex items-center justify-between">
|
|
212
|
-
<div className="flex items-center gap-3">
|
|
213
|
-
<Badge variant={TIER_BADGE_VARIANT[tier]}>
|
|
214
|
-
{TIER_LABELS[tier]}
|
|
215
|
-
</Badge>
|
|
216
|
-
{!isPremium && (
|
|
217
|
-
<span className="text-sm text-muted-foreground">Free forever</span>
|
|
218
|
-
)}
|
|
219
|
-
{license?.expiresAt && (
|
|
220
|
-
<span className="text-sm text-muted-foreground">
|
|
221
|
-
Renews {new Date(license.expiresAt).toLocaleDateString()}
|
|
222
|
-
</span>
|
|
223
|
-
)}
|
|
224
|
-
</div>
|
|
225
|
-
{isPremium && license?.email && (
|
|
226
|
-
<Button variant="outline" size="sm" asChild>
|
|
227
|
-
<a href={`/api/license/portal?email=${encodeURIComponent(license.email)}`} target="_blank" rel="noopener">
|
|
228
|
-
Manage Billing <ExternalLink className="ml-1 h-3 w-3" />
|
|
229
|
-
</a>
|
|
230
|
-
</Button>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
|
|
234
|
-
{/* Usage Summary */}
|
|
235
|
-
{usage && (
|
|
236
|
-
<div className="grid grid-cols-2 gap-3">
|
|
237
|
-
<UsageCard icon={Brain} label="Agent Memories" current={usage.agentMemories.current} limit={usage.agentMemories.limit} resourceKey="agentMemories" />
|
|
238
|
-
<UsageCard icon={BarChart3} label="Context Versions" current={usage.contextVersions.current} limit={usage.contextVersions.limit} resourceKey="contextVersions" />
|
|
239
|
-
<UsageCard icon={Calendar} label="Active Schedules" current={usage.activeSchedules.current} limit={usage.activeSchedules.limit} resourceKey="activeSchedules" />
|
|
240
|
-
<UsageCard icon={Zap} label="Parallel Workflows" current={usage.parallelWorkflows.current} limit={usage.parallelWorkflows.limit} resourceKey="parallelWorkflows" />
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
{/* Tier Comparison */}
|
|
245
|
-
{!isPremium && (
|
|
246
|
-
<div className="space-y-3">
|
|
247
|
-
<div className="flex items-center justify-between">
|
|
248
|
-
<h4 className="text-sm font-medium">Upgrade your plan</h4>
|
|
249
|
-
<div className="flex items-center gap-1 rounded-lg border p-0.5">
|
|
250
|
-
<button
|
|
251
|
-
type="button"
|
|
252
|
-
className={`px-3 py-1 text-xs rounded-md transition-colors ${billingPeriod === "monthly" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`}
|
|
253
|
-
onClick={() => setBillingPeriod("monthly")}
|
|
254
|
-
>
|
|
255
|
-
Monthly
|
|
256
|
-
</button>
|
|
257
|
-
<button
|
|
258
|
-
type="button"
|
|
259
|
-
className={`px-3 py-1 text-xs rounded-md transition-colors ${billingPeriod === "annual" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`}
|
|
260
|
-
onClick={() => setBillingPeriod("annual")}
|
|
261
|
-
>
|
|
262
|
-
Annual
|
|
263
|
-
</button>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
<div className="grid grid-cols-3 gap-3">
|
|
267
|
-
{/* Solo */}
|
|
268
|
-
<div className={`surface-card-muted rounded-lg p-4 flex flex-col ${tier === "solo" ? "ring-1 ring-primary" : ""}`}>
|
|
269
|
-
<div className="space-y-3 flex-1">
|
|
270
|
-
<div>
|
|
271
|
-
<div className="flex items-center justify-between">
|
|
272
|
-
<span className="text-sm font-semibold">Solo</span>
|
|
273
|
-
{tier === "solo" && <Badge variant="outline" className="text-[10px]">Current Plan</Badge>}
|
|
274
|
-
</div>
|
|
275
|
-
<p className="text-lg font-bold mt-1">
|
|
276
|
-
${TIER_PRICING.solo[billingPeriod]}
|
|
277
|
-
<span className="text-xs font-normal text-muted-foreground">
|
|
278
|
-
{billingPeriod === "monthly" ? "/mo" : "/yr"}
|
|
279
|
-
</span>
|
|
280
|
-
</p>
|
|
281
|
-
{billingPeriod === "annual" && (
|
|
282
|
-
<p className="text-[10px] text-primary font-medium mt-0.5">
|
|
283
|
-
Save ${TIER_PRICING.solo.monthly * 12 - TIER_PRICING.solo.annual}/yr
|
|
284
|
-
</p>
|
|
285
|
-
)}
|
|
286
|
-
</div>
|
|
287
|
-
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
|
288
|
-
For power users who need room to grow. 4x the memory, longer history, and marketplace access.
|
|
289
|
-
</p>
|
|
290
|
-
<ul className="space-y-1">
|
|
291
|
-
<TierBenefit icon={Brain} text="200 memories per profile" />
|
|
292
|
-
<TierBenefit icon={Calendar} text="20 active schedules" />
|
|
293
|
-
<TierBenefit icon={Store} text="Import marketplace blueprints" />
|
|
294
|
-
</ul>
|
|
295
|
-
</div>
|
|
296
|
-
<Button
|
|
297
|
-
size="sm"
|
|
298
|
-
variant="outline"
|
|
299
|
-
className="w-full mt-4"
|
|
300
|
-
disabled={checkoutLoading || tier === "solo"}
|
|
301
|
-
onClick={() => handleUpgrade("solo")}
|
|
302
|
-
>
|
|
303
|
-
{tier === "solo" ? "Current Plan" : checkoutLoading ? "Loading..." : "Get Solo"}
|
|
304
|
-
</Button>
|
|
305
|
-
</div>
|
|
306
|
-
|
|
307
|
-
{/* Operator */}
|
|
308
|
-
<div className={`surface-card-muted rounded-lg p-4 flex flex-col ${tier === "operator" ? "ring-1 ring-primary" : "ring-1 ring-primary/30"}`}>
|
|
309
|
-
<div className="space-y-3 flex-1">
|
|
310
|
-
<div className="flex items-center justify-between">
|
|
311
|
-
<span className="text-sm font-semibold">Operator</span>
|
|
312
|
-
<div className="flex items-center gap-1.5">
|
|
313
|
-
{tier === "operator" && <Badge variant="outline" className="text-[10px]">Current Plan</Badge>}
|
|
314
|
-
<Badge variant="secondary" className="text-[10px]">Popular</Badge>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
<p className="text-lg font-bold">
|
|
318
|
-
${TIER_PRICING.operator[billingPeriod]}
|
|
319
|
-
<span className="text-xs font-normal text-muted-foreground">
|
|
320
|
-
{billingPeriod === "monthly" ? "/mo" : "/yr"}
|
|
321
|
-
</span>
|
|
322
|
-
</p>
|
|
323
|
-
{billingPeriod === "annual" && (
|
|
324
|
-
<p className="text-[10px] text-primary font-medium">
|
|
325
|
-
Save ${TIER_PRICING.operator.monthly * 12 - TIER_PRICING.operator.annual}/yr
|
|
326
|
-
</p>
|
|
327
|
-
)}
|
|
328
|
-
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
|
329
|
-
For professionals who run AI at scale. Full analytics, cloud sync, and marketplace publishing.
|
|
330
|
-
</p>
|
|
331
|
-
<ul className="space-y-1">
|
|
332
|
-
<TierBenefit icon={TrendingUp} text="ROI analytics dashboard" />
|
|
333
|
-
<TierBenefit icon={Cloud} text="Encrypted cloud sync" />
|
|
334
|
-
<TierBenefit icon={Store} text="Publish to marketplace" />
|
|
335
|
-
<TierBenefit icon={Brain} text="500 memories per profile" />
|
|
336
|
-
</ul>
|
|
337
|
-
</div>
|
|
338
|
-
<Button
|
|
339
|
-
size="sm"
|
|
340
|
-
className="w-full mt-4"
|
|
341
|
-
disabled={checkoutLoading || tier === "operator"}
|
|
342
|
-
onClick={() => handleUpgrade("operator")}
|
|
343
|
-
>
|
|
344
|
-
{tier === "operator" ? "Current Plan" : checkoutLoading ? "Loading..." : "Get Operator"}
|
|
345
|
-
</Button>
|
|
346
|
-
</div>
|
|
347
|
-
|
|
348
|
-
{/* Scale */}
|
|
349
|
-
<div className={`surface-card-muted rounded-lg p-4 flex flex-col ${tier === "scale" ? "ring-1 ring-primary" : ""}`}>
|
|
350
|
-
<div className="space-y-3 flex-1">
|
|
351
|
-
<div>
|
|
352
|
-
<div className="flex items-center justify-between">
|
|
353
|
-
<span className="text-sm font-semibold">Scale</span>
|
|
354
|
-
{tier === "scale" && <Badge variant="outline" className="text-[10px]">Current Plan</Badge>}
|
|
355
|
-
</div>
|
|
356
|
-
<p className="text-lg font-bold mt-1">
|
|
357
|
-
${TIER_PRICING.scale[billingPeriod]}
|
|
358
|
-
<span className="text-xs font-normal text-muted-foreground">
|
|
359
|
-
{billingPeriod === "monthly" ? "/mo" : "/yr"}
|
|
360
|
-
</span>
|
|
361
|
-
</p>
|
|
362
|
-
{billingPeriod === "annual" && (
|
|
363
|
-
<p className="text-[10px] text-primary font-medium mt-0.5">
|
|
364
|
-
Save ${TIER_PRICING.scale.monthly * 12 - TIER_PRICING.scale.annual}/yr
|
|
365
|
-
</p>
|
|
366
|
-
)}
|
|
367
|
-
</div>
|
|
368
|
-
<p className="text-[11px] leading-relaxed text-muted-foreground">
|
|
369
|
-
No limits, no compromises. Unlimited everything with featured marketplace placement.
|
|
370
|
-
</p>
|
|
371
|
-
<ul className="space-y-1">
|
|
372
|
-
<TierBenefit icon={Zap} text="Unlimited memories & schedules" />
|
|
373
|
-
<TierBenefit icon={Store} text="Featured marketplace listings" />
|
|
374
|
-
<TierBenefit icon={Calendar} text="Unlimited history retention" />
|
|
375
|
-
</ul>
|
|
376
|
-
</div>
|
|
377
|
-
<Button
|
|
378
|
-
size="sm"
|
|
379
|
-
variant="outline"
|
|
380
|
-
className="w-full mt-4"
|
|
381
|
-
disabled={checkoutLoading || tier === "scale"}
|
|
382
|
-
onClick={() => handleUpgrade("scale")}
|
|
383
|
-
>
|
|
384
|
-
{tier === "scale" ? "Current Plan" : checkoutLoading ? "Loading..." : "Get Scale"}
|
|
385
|
-
</Button>
|
|
386
|
-
</div>
|
|
387
|
-
</div>
|
|
388
|
-
</div>
|
|
389
|
-
)}
|
|
390
|
-
|
|
391
|
-
{/* License Key Activation (fallback for manual entry) */}
|
|
392
|
-
{!isPremium && (
|
|
393
|
-
<ActivationForm onActivated={fetchStatus} />
|
|
394
|
-
)}
|
|
395
|
-
|
|
396
|
-
{/* License Info */}
|
|
397
|
-
{license?.email && (
|
|
398
|
-
<p className="text-xs text-muted-foreground">
|
|
399
|
-
Licensed to {license.email}
|
|
400
|
-
{license.status === "grace" && (
|
|
401
|
-
<span className="text-amber-500 ml-2">
|
|
402
|
-
(Offline grace period — reconnect to validate)
|
|
403
|
-
</span>
|
|
404
|
-
)}
|
|
405
|
-
</p>
|
|
406
|
-
)}
|
|
407
|
-
</CardContent>
|
|
408
|
-
</Card>
|
|
409
|
-
);
|
|
410
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { BarChart3, Shield } from "lucide-react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import {
|
|
7
|
-
Card,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardHeader,
|
|
11
|
-
CardTitle,
|
|
12
|
-
} from "@/components/ui/card";
|
|
13
|
-
import { Switch } from "@/components/ui/switch";
|
|
14
|
-
|
|
15
|
-
export function TelemetrySection() {
|
|
16
|
-
const [enabled, setEnabled] = useState(false);
|
|
17
|
-
const [loading, setLoading] = useState(true);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
fetch("/api/settings/telemetry")
|
|
21
|
-
.then((r) => (r.ok ? r.json() : { enabled: false }))
|
|
22
|
-
.then((d) => setEnabled(d.enabled === true))
|
|
23
|
-
.catch(() => {})
|
|
24
|
-
.finally(() => setLoading(false));
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
async function handleToggle(checked: boolean) {
|
|
28
|
-
setEnabled(checked);
|
|
29
|
-
try {
|
|
30
|
-
const res = await fetch("/api/settings/telemetry", {
|
|
31
|
-
method: "POST",
|
|
32
|
-
headers: { "Content-Type": "application/json" },
|
|
33
|
-
body: JSON.stringify({ enabled: checked }),
|
|
34
|
-
});
|
|
35
|
-
if (res.ok) {
|
|
36
|
-
toast.success(checked ? "Telemetry enabled — thank you!" : "Telemetry disabled");
|
|
37
|
-
} else {
|
|
38
|
-
setEnabled(!checked); // Revert on failure
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
setEnabled(!checked);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Card>
|
|
47
|
-
<CardHeader>
|
|
48
|
-
<CardTitle className="flex items-center gap-2">
|
|
49
|
-
<BarChart3 className="h-5 w-5" />
|
|
50
|
-
Anonymous Telemetry
|
|
51
|
-
</CardTitle>
|
|
52
|
-
<CardDescription>
|
|
53
|
-
Help improve Stagent by sharing anonymized usage data
|
|
54
|
-
</CardDescription>
|
|
55
|
-
</CardHeader>
|
|
56
|
-
<CardContent className="space-y-4">
|
|
57
|
-
<div className="flex items-center justify-between">
|
|
58
|
-
<div className="space-y-1">
|
|
59
|
-
<p className="text-sm font-medium">Share usage data</p>
|
|
60
|
-
<p className="text-xs text-muted-foreground">
|
|
61
|
-
Activity types, model usage, and outcome rates. No task content, project names, or personal data.
|
|
62
|
-
</p>
|
|
63
|
-
</div>
|
|
64
|
-
<Switch
|
|
65
|
-
checked={enabled}
|
|
66
|
-
onCheckedChange={handleToggle}
|
|
67
|
-
disabled={loading}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
<div className="flex items-start gap-2 text-xs text-muted-foreground">
|
|
71
|
-
<Shield className="h-3.5 w-3.5 shrink-0 mt-0.5" />
|
|
72
|
-
<span>
|
|
73
|
-
Data is opt-in, anonymized, and never includes task descriptions, file contents, or email addresses.
|
|
74
|
-
You can disable this at any time.
|
|
75
|
-
</span>
|
|
76
|
-
</div>
|
|
77
|
-
</CardContent>
|
|
78
|
-
</Card>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { Lock } from "lucide-react";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import { cn } from "@/lib/utils";
|
|
7
|
-
import { TIER_LABELS, type LicenseTier } from "@/lib/license/tier-limits";
|
|
8
|
-
|
|
9
|
-
interface PremiumGateOverlayProps {
|
|
10
|
-
feature: string;
|
|
11
|
-
requiredTier: LicenseTier;
|
|
12
|
-
children: React.ReactNode;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Content-fade overlay for Community users viewing premium features.
|
|
18
|
-
* Shows blurred/faded content with a lock card and upgrade CTA.
|
|
19
|
-
*/
|
|
20
|
-
export function PremiumGateOverlay({
|
|
21
|
-
feature,
|
|
22
|
-
requiredTier,
|
|
23
|
-
children,
|
|
24
|
-
className,
|
|
25
|
-
}: PremiumGateOverlayProps) {
|
|
26
|
-
return (
|
|
27
|
-
<div className={cn("relative", className)}>
|
|
28
|
-
{/* Faded content */}
|
|
29
|
-
<div className="opacity-30 pointer-events-none select-none blur-[2px]" aria-hidden>
|
|
30
|
-
{children}
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
{/* Lock card */}
|
|
34
|
-
<div className="absolute inset-0 flex items-center justify-center">
|
|
35
|
-
<div className="surface-card-muted rounded-lg border p-6 text-center space-y-3 max-w-sm shadow-lg">
|
|
36
|
-
<Lock className="h-8 w-8 mx-auto text-muted-foreground" />
|
|
37
|
-
<h3 className="text-sm font-medium">{feature}</h3>
|
|
38
|
-
<p className="text-xs text-muted-foreground">
|
|
39
|
-
This feature requires the {TIER_LABELS[requiredTier]} tier or above.
|
|
40
|
-
</p>
|
|
41
|
-
<Button size="sm" asChild>
|
|
42
|
-
<Link href={`/settings?highlight=${requiredTier}`}>
|
|
43
|
-
Upgrade to {TIER_LABELS[requiredTier]}
|
|
44
|
-
</Link>
|
|
45
|
-
</Button>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { CalendarOff } from "lucide-react";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
Dialog,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogDescription,
|
|
10
|
-
DialogFooter,
|
|
11
|
-
DialogHeader,
|
|
12
|
-
DialogTitle,
|
|
13
|
-
} from "@/components/ui/dialog";
|
|
14
|
-
import { TIER_LABELS, type LicenseTier } from "@/lib/license/tier-limits";
|
|
15
|
-
|
|
16
|
-
interface ScheduleGateDialogProps {
|
|
17
|
-
open: boolean;
|
|
18
|
-
onClose: () => void;
|
|
19
|
-
current: number;
|
|
20
|
-
max: number;
|
|
21
|
-
requiredTier: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Modal intercept when a Community user tries to create a schedule
|
|
26
|
-
* past their limit. Shows clear messaging and an upgrade path.
|
|
27
|
-
*/
|
|
28
|
-
export function ScheduleGateDialog({
|
|
29
|
-
open,
|
|
30
|
-
onClose,
|
|
31
|
-
current,
|
|
32
|
-
max,
|
|
33
|
-
requiredTier,
|
|
34
|
-
}: ScheduleGateDialogProps) {
|
|
35
|
-
const tierLabel = TIER_LABELS[requiredTier as LicenseTier] ?? requiredTier;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
|
|
39
|
-
<DialogContent className="sm:max-w-md">
|
|
40
|
-
<DialogHeader>
|
|
41
|
-
<div className="flex items-center gap-2">
|
|
42
|
-
<CalendarOff className="h-5 w-5 text-muted-foreground" />
|
|
43
|
-
<DialogTitle>Schedule limit reached</DialogTitle>
|
|
44
|
-
</div>
|
|
45
|
-
<DialogDescription>
|
|
46
|
-
You have {current} of {max} active schedules on the{" "}
|
|
47
|
-
{TIER_LABELS.community} tier. Upgrade to {tierLabel} for up to{" "}
|
|
48
|
-
{requiredTier === "scale" ? "unlimited" : "more"} active schedules.
|
|
49
|
-
</DialogDescription>
|
|
50
|
-
</DialogHeader>
|
|
51
|
-
<DialogFooter className="flex gap-2 sm:gap-0">
|
|
52
|
-
<Button variant="outline" onClick={onClose}>
|
|
53
|
-
Cancel
|
|
54
|
-
</Button>
|
|
55
|
-
<Button asChild>
|
|
56
|
-
<Link href={`/settings?highlight=${requiredTier}`}>
|
|
57
|
-
Upgrade to {tierLabel}
|
|
58
|
-
</Link>
|
|
59
|
-
</Button>
|
|
60
|
-
</DialogFooter>
|
|
61
|
-
</DialogContent>
|
|
62
|
-
</Dialog>
|
|
63
|
-
);
|
|
64
|
-
}
|