ui: show no-agents banner on dashboard with link to onboarding step 2

When a company has no agents (e.g. user exited onboarding early), the
dashboard now shows an amber banner: "You have no agents. Create one
here" that opens the onboarding wizard directly at step 2 (agent
creation), skipping the company creation step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dotta
2026-03-03 11:37:19 -06:00
parent 1301bdd18a
commit 88149bbd29
5 changed files with 56 additions and 11 deletions

View File

@@ -186,7 +186,7 @@ function NoCompaniesStartPage() {
Get started by creating a company.
</p>
<div className="mt-4">
<Button onClick={openOnboarding}>New Company</Button>
<Button onClick={() => openOnboarding()}>New Company</Button>
</div>
</div>
<OnboardingWizard />

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useDialog } from "../context/DialogContext";
@@ -48,12 +48,15 @@ const DEFAULT_TASK_DESCRIPTION = `Setup yourself as the CEO. Use the ceo persona
Ensure you have a folder agents/ceo and then download this AGENTS.md as well as the sibling HEARTBEAT.md, SOUL.md, and TOOLS.md. and set that AGENTS.md as the path to your agents instruction file`;
export function OnboardingWizard() {
const { onboardingOpen, closeOnboarding } = useDialog();
const { setSelectedCompanyId } = useCompany();
const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog();
const { selectedCompanyId, companies, setSelectedCompanyId } = useCompany();
const queryClient = useQueryClient();
const navigate = useNavigate();
const [step, setStep] = useState<Step>(1);
const initialStep = onboardingOptions.initialStep ?? 1;
const existingCompanyId = onboardingOptions.companyId;
const [step, setStep] = useState<Step>(initialStep);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [modelOpen, setModelOpen] = useState(false);
@@ -75,12 +78,25 @@ export function OnboardingWizard() {
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
const [taskDescription, setTaskDescription] = useState(DEFAULT_TASK_DESCRIPTION);
// Created entity IDs
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(null);
// Created entity IDs — pre-populate from existing company when skipping step 1
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(existingCompanyId ?? null);
const [createdCompanyPrefix, setCreatedCompanyPrefix] = useState<string | null>(null);
const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
const [createdIssueRef, setCreatedIssueRef] = useState<string | null>(null);
// Sync step and company when onboarding opens with options
useEffect(() => {
if (onboardingOpen) {
const cId = onboardingOptions.companyId ?? null;
setStep(onboardingOptions.initialStep ?? 1);
setCreatedCompanyId(cId);
if (cId) {
const company = companies.find((c) => c.id === cId);
if (company) setCreatedCompanyPrefix(company.issuePrefix);
}
}
}, [onboardingOpen, onboardingOptions, companies]);
const { data: adapterModels } = useQuery({
queryKey: ["adapter-models", adapterType],
queryFn: () => agentsApi.adapterModels(adapterType),
@@ -634,7 +650,7 @@ export function OnboardingWizard() {
{/* Footer navigation */}
<div className="flex items-center justify-between mt-8">
<div>
{step > 1 && (
{step > 1 && step > (onboardingOptions.initialStep ?? 1) && (
<Button
variant="ghost"
size="sm"

View File

@@ -11,6 +11,11 @@ interface NewGoalDefaults {
parentId?: string;
}
interface OnboardingOptions {
initialStep?: 1 | 2 | 3 | 4;
companyId?: string;
}
interface DialogContextValue {
newIssueOpen: boolean;
newIssueDefaults: NewIssueDefaults;
@@ -27,7 +32,8 @@ interface DialogContextValue {
openNewAgent: () => void;
closeNewAgent: () => void;
onboardingOpen: boolean;
openOnboarding: () => void;
onboardingOptions: OnboardingOptions;
openOnboarding: (options?: OnboardingOptions) => void;
closeOnboarding: () => void;
}
@@ -41,6 +47,7 @@ export function DialogProvider({ children }: { children: ReactNode }) {
const [newGoalDefaults, setNewGoalDefaults] = useState<NewGoalDefaults>({});
const [newAgentOpen, setNewAgentOpen] = useState(false);
const [onboardingOpen, setOnboardingOpen] = useState(false);
const [onboardingOptions, setOnboardingOptions] = useState<OnboardingOptions>({});
const openNewIssue = useCallback((defaults: NewIssueDefaults = {}) => {
setNewIssueDefaults(defaults);
@@ -78,12 +85,14 @@ export function DialogProvider({ children }: { children: ReactNode }) {
setNewAgentOpen(false);
}, []);
const openOnboarding = useCallback(() => {
const openOnboarding = useCallback((options: OnboardingOptions = {}) => {
setOnboardingOptions(options);
setOnboardingOpen(true);
}, []);
const closeOnboarding = useCallback(() => {
setOnboardingOpen(false);
setOnboardingOptions({});
}, []);
return (
@@ -104,6 +113,7 @@ export function DialogProvider({ children }: { children: ReactNode }) {
openNewAgent,
closeNewAgent,
onboardingOpen,
onboardingOptions,
openOnboarding,
closeOnboarding,
}}

View File

@@ -90,7 +90,7 @@ export function Companies() {
return (
<div className="space-y-6">
<div className="flex items-center justify-end">
<Button size="sm" onClick={openOnboarding}>
<Button size="sm" onClick={() => openOnboarding()}>
<Plus className="h-3.5 w-3.5 mr-1.5" />
New Company
</Button>

View File

@@ -182,10 +182,29 @@ export function Dashboard() {
return <PageSkeleton variant="dashboard" />;
}
const hasNoAgents = agents !== undefined && agents.length === 0;
return (
<div className="space-y-6">
{error && <p className="text-sm text-destructive">{error.message}</p>}
{hasNoAgents && (
<div className="flex items-center justify-between gap-3 rounded-md border border-amber-300 bg-amber-50 px-4 py-3 dark:border-amber-500/25 dark:bg-amber-950/60">
<div className="flex items-center gap-2.5">
<Bot className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0" />
<p className="text-sm text-amber-900 dark:text-amber-100">
You have no agents.
</p>
</div>
<button
onClick={() => openOnboarding({ initialStep: 2, companyId: selectedCompanyId! })}
className="text-sm font-medium text-amber-700 hover:text-amber-900 dark:text-amber-300 dark:hover:text-amber-100 underline underline-offset-2 shrink-0"
>
Create one here
</button>
</div>
)}
<ActiveAgentsPanel companyId={selectedCompanyId!} />
{data && (