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.
|
Get started by creating a company.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<Button onClick={openOnboarding}>New Company</Button>
|
<Button onClick={() => openOnboarding()}>New Company</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<OnboardingWizard />
|
<OnboardingWizard />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useDialog } from "../context/DialogContext";
|
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`;
|
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() {
|
export function OnboardingWizard() {
|
||||||
const { onboardingOpen, closeOnboarding } = useDialog();
|
const { onboardingOpen, onboardingOptions, closeOnboarding } = useDialog();
|
||||||
const { setSelectedCompanyId } = useCompany();
|
const { selectedCompanyId, companies, setSelectedCompanyId } = useCompany();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
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 [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [modelOpen, setModelOpen] = useState(false);
|
const [modelOpen, setModelOpen] = useState(false);
|
||||||
@@ -75,12 +78,25 @@ export function OnboardingWizard() {
|
|||||||
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
|
const [taskTitle, setTaskTitle] = useState("Create your CEO HEARTBEAT.md");
|
||||||
const [taskDescription, setTaskDescription] = useState(DEFAULT_TASK_DESCRIPTION);
|
const [taskDescription, setTaskDescription] = useState(DEFAULT_TASK_DESCRIPTION);
|
||||||
|
|
||||||
// Created entity IDs
|
// Created entity IDs — pre-populate from existing company when skipping step 1
|
||||||
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(null);
|
const [createdCompanyId, setCreatedCompanyId] = useState<string | null>(existingCompanyId ?? null);
|
||||||
const [createdCompanyPrefix, setCreatedCompanyPrefix] = useState<string | null>(null);
|
const [createdCompanyPrefix, setCreatedCompanyPrefix] = useState<string | null>(null);
|
||||||
const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
|
const [createdAgentId, setCreatedAgentId] = useState<string | null>(null);
|
||||||
const [createdIssueRef, setCreatedIssueRef] = 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({
|
const { data: adapterModels } = useQuery({
|
||||||
queryKey: ["adapter-models", adapterType],
|
queryKey: ["adapter-models", adapterType],
|
||||||
queryFn: () => agentsApi.adapterModels(adapterType),
|
queryFn: () => agentsApi.adapterModels(adapterType),
|
||||||
@@ -634,7 +650,7 @@ export function OnboardingWizard() {
|
|||||||
{/* Footer navigation */}
|
{/* Footer navigation */}
|
||||||
<div className="flex items-center justify-between mt-8">
|
<div className="flex items-center justify-between mt-8">
|
||||||
<div>
|
<div>
|
||||||
{step > 1 && (
|
{step > 1 && step > (onboardingOptions.initialStep ?? 1) && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ interface NewGoalDefaults {
|
|||||||
parentId?: string;
|
parentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OnboardingOptions {
|
||||||
|
initialStep?: 1 | 2 | 3 | 4;
|
||||||
|
companyId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface DialogContextValue {
|
interface DialogContextValue {
|
||||||
newIssueOpen: boolean;
|
newIssueOpen: boolean;
|
||||||
newIssueDefaults: NewIssueDefaults;
|
newIssueDefaults: NewIssueDefaults;
|
||||||
@@ -27,7 +32,8 @@ interface DialogContextValue {
|
|||||||
openNewAgent: () => void;
|
openNewAgent: () => void;
|
||||||
closeNewAgent: () => void;
|
closeNewAgent: () => void;
|
||||||
onboardingOpen: boolean;
|
onboardingOpen: boolean;
|
||||||
openOnboarding: () => void;
|
onboardingOptions: OnboardingOptions;
|
||||||
|
openOnboarding: (options?: OnboardingOptions) => void;
|
||||||
closeOnboarding: () => void;
|
closeOnboarding: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +47,7 @@ export function DialogProvider({ children }: { children: ReactNode }) {
|
|||||||
const [newGoalDefaults, setNewGoalDefaults] = useState<NewGoalDefaults>({});
|
const [newGoalDefaults, setNewGoalDefaults] = useState<NewGoalDefaults>({});
|
||||||
const [newAgentOpen, setNewAgentOpen] = useState(false);
|
const [newAgentOpen, setNewAgentOpen] = useState(false);
|
||||||
const [onboardingOpen, setOnboardingOpen] = useState(false);
|
const [onboardingOpen, setOnboardingOpen] = useState(false);
|
||||||
|
const [onboardingOptions, setOnboardingOptions] = useState<OnboardingOptions>({});
|
||||||
|
|
||||||
const openNewIssue = useCallback((defaults: NewIssueDefaults = {}) => {
|
const openNewIssue = useCallback((defaults: NewIssueDefaults = {}) => {
|
||||||
setNewIssueDefaults(defaults);
|
setNewIssueDefaults(defaults);
|
||||||
@@ -78,12 +85,14 @@ export function DialogProvider({ children }: { children: ReactNode }) {
|
|||||||
setNewAgentOpen(false);
|
setNewAgentOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const openOnboarding = useCallback(() => {
|
const openOnboarding = useCallback((options: OnboardingOptions = {}) => {
|
||||||
|
setOnboardingOptions(options);
|
||||||
setOnboardingOpen(true);
|
setOnboardingOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeOnboarding = useCallback(() => {
|
const closeOnboarding = useCallback(() => {
|
||||||
setOnboardingOpen(false);
|
setOnboardingOpen(false);
|
||||||
|
setOnboardingOptions({});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -104,6 +113,7 @@ export function DialogProvider({ children }: { children: ReactNode }) {
|
|||||||
openNewAgent,
|
openNewAgent,
|
||||||
closeNewAgent,
|
closeNewAgent,
|
||||||
onboardingOpen,
|
onboardingOpen,
|
||||||
|
onboardingOptions,
|
||||||
openOnboarding,
|
openOnboarding,
|
||||||
closeOnboarding,
|
closeOnboarding,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function Companies() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-end">
|
<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" />
|
<Plus className="h-3.5 w-3.5 mr-1.5" />
|
||||||
New Company
|
New Company
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -182,10 +182,29 @@ export function Dashboard() {
|
|||||||
return <PageSkeleton variant="dashboard" />;
|
return <PageSkeleton variant="dashboard" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasNoAgents = agents !== undefined && agents.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
{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!} />
|
<ActiveAgentsPanel companyId={selectedCompanyId!} />
|
||||||
|
|
||||||
{data && (
|
{data && (
|
||||||
|
|||||||
Reference in New Issue
Block a user