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:
@@ -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 />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user