feat(phase-4): WS2 + WS3 — Developer Portal (Next.js 14) and CLI tool (sentryagent)
WS2: Developer Portal (portal/) - Standalone Next.js 14 + Tailwind CSS app — independent deployment - Home page: hero, feature grid, CTA to /get-started - /pricing: free tier limits table (10 agents, 1k calls/day) + paid tier CTA - /sdks: all 4 SDKs (Node.js, Python, Go, Java) with install + code examples - /api-explorer: Swagger UI from NEXT_PUBLIC_API_URL/openapi.json, persistAuthorization - /get-started: 4-step wizard (setup → register agent → credentials → SDK snippet) - Shared Nav component with active-link highlighting - Build: 8/8 static pages, zero TypeScript errors WS3: CLI Tool (cli/ — npm package: sentryagent) - configure, register-agent, list-agents, issue-token, rotate-credentials, tail-audit-log - Auto OAuth2 token fetch + 30s-buffer cache via client_credentials flow - chalk-formatted table output, confirmation prompts, bounded audit log dedup - bash + zsh shell completion scripts - README with installation, all commands, and completion setup - Build: tsc clean, node dist/index.js --help verified Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
674
portal/components/GetStartedWizard.tsx
Normal file
674
portal/components/GetStartedWizard.tsx
Normal file
@@ -0,0 +1,674 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type Sdk = 'nodejs' | 'python' | 'go' | 'java';
|
||||
|
||||
interface WizardState {
|
||||
step: 1 | 2 | 3 | 4;
|
||||
agentName: string;
|
||||
agentId: string | null;
|
||||
clientId: string | null;
|
||||
clientSecret: string | null;
|
||||
selectedSdk: Sdk;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface AgentCreateResponse {
|
||||
agentId: string;
|
||||
}
|
||||
|
||||
interface CredentialsResponse {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SDK code snippets
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildSnippet(
|
||||
sdk: Sdk,
|
||||
apiUrl: string,
|
||||
clientId: string,
|
||||
clientSecret: string
|
||||
): string {
|
||||
switch (sdk) {
|
||||
case 'nodejs':
|
||||
return `import { AgentIdPClient } from '@sentryagent/idp-sdk';
|
||||
|
||||
const client = new AgentIdPClient({
|
||||
apiUrl: '${apiUrl}',
|
||||
clientId: '${clientId}',
|
||||
clientSecret: '${clientSecret}',
|
||||
});
|
||||
|
||||
const { accessToken } = await client.tokens.issue();
|
||||
console.log('Access token:', accessToken);`;
|
||||
|
||||
case 'python':
|
||||
return `from sentryagent_idp import AgentIdPClient
|
||||
|
||||
client = AgentIdPClient(
|
||||
api_url="${apiUrl}",
|
||||
client_id="${clientId}",
|
||||
client_secret="${clientSecret}",
|
||||
)
|
||||
|
||||
token_response = client.tokens.issue()
|
||||
print("Access token:", token_response.access_token)`;
|
||||
|
||||
case 'go':
|
||||
return `import idp "github.com/sentryagent/idp-sdk-go"
|
||||
|
||||
client := idp.NewClient(idp.Config{
|
||||
APIURL: "${apiUrl}",
|
||||
ClientID: "${clientId}",
|
||||
ClientSecret: "${clientSecret}",
|
||||
})
|
||||
|
||||
token, err := client.Tokens.Issue(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Access token:", token.AccessToken)`;
|
||||
|
||||
case 'java':
|
||||
return `AgentIdPClient client = AgentIdPClient.builder()
|
||||
.apiUrl("${apiUrl}")
|
||||
.clientId("${clientId}")
|
||||
.clientSecret("${clientSecret}")
|
||||
.build();
|
||||
|
||||
TokenResponse token = client.tokens().issue();
|
||||
System.out.println("Access token: " + token.getAccessToken());`;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared UI helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function StepIndicator({
|
||||
current,
|
||||
total,
|
||||
}: {
|
||||
current: number;
|
||||
total: number;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div className="mb-8 flex items-center gap-2">
|
||||
{Array.from({ length: total }, (_, i) => i + 1).map((n) => (
|
||||
<React.Fragment key={n}>
|
||||
<div
|
||||
className={[
|
||||
'flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold',
|
||||
n < current
|
||||
? 'bg-brand-600 text-white'
|
||||
: n === current
|
||||
? 'border-2 border-brand-600 bg-brand-50 text-brand-700'
|
||||
: 'bg-slate-200 text-slate-500',
|
||||
].join(' ')}
|
||||
>
|
||||
{n < current ? '✓' : n}
|
||||
</div>
|
||||
{n < total && (
|
||||
<div
|
||||
className={[
|
||||
'h-0.5 flex-1',
|
||||
n < current ? 'bg-brand-600' : 'bg-slate-200',
|
||||
].join(' ')}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyButton({ text }: { text: string }): React.ReactElement {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async (): Promise<void> => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => void handleCopy()}
|
||||
className="ml-2 rounded bg-slate-100 px-2 py-1 text-xs font-medium text-slate-600 transition-colors hover:bg-slate-200"
|
||||
>
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ErrorAlert({ message }: { message: string }): React.ReactElement {
|
||||
return (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Step components
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function Step1AccountSetup({
|
||||
onNext,
|
||||
}: {
|
||||
onNext: () => void;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-2 text-2xl font-bold text-slate-900">
|
||||
Step 1: Account Setup
|
||||
</h2>
|
||||
<p className="mb-6 text-slate-600">
|
||||
Before registering your first agent, make sure you have the AgentIdP
|
||||
server running.
|
||||
</p>
|
||||
|
||||
<ol className="mb-8 space-y-4">
|
||||
{[
|
||||
{
|
||||
n: 1,
|
||||
title: 'Clone the repository',
|
||||
code: 'git clone https://github.com/sentryagent/sentryagent-idp.git',
|
||||
},
|
||||
{
|
||||
n: 2,
|
||||
title: 'Copy environment variables',
|
||||
code: 'cp .env.example .env',
|
||||
},
|
||||
{
|
||||
n: 3,
|
||||
title: 'Start the server',
|
||||
code: 'docker compose up -d && npm run db:migrate',
|
||||
},
|
||||
{
|
||||
n: 4,
|
||||
title: 'Verify the server is healthy',
|
||||
code: 'curl http://localhost:3000/health',
|
||||
},
|
||||
].map(({ n, title, code }) => (
|
||||
<li key={n} className="flex gap-4">
|
||||
<span className="mt-0.5 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-brand-600 text-xs font-bold text-white">
|
||||
{n}
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<p className="mb-1 font-medium text-slate-800">{title}</p>
|
||||
<div className="flex items-center rounded-lg bg-slate-900 px-3 py-2">
|
||||
<code className="flex-1 text-sm text-slate-100">{code}</code>
|
||||
<CopyButton text={code} />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="rounded-lg bg-brand-600 px-6 py-2.5 font-semibold text-white transition-colors hover:bg-brand-700"
|
||||
>
|
||||
My server is running →
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Step2RegisterAgent({
|
||||
agentName,
|
||||
onAgentNameChange,
|
||||
agentId,
|
||||
loading,
|
||||
error,
|
||||
onRegister,
|
||||
onNext,
|
||||
}: {
|
||||
agentName: string;
|
||||
onAgentNameChange: (v: string) => void;
|
||||
agentId: string | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
onRegister: () => void;
|
||||
onNext: () => void;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-2 text-2xl font-bold text-slate-900">
|
||||
Step 2: Register Your Agent
|
||||
</h2>
|
||||
<p className="mb-6 text-slate-600">
|
||||
Give your agent a name and register it with AgentIdP. You will receive a
|
||||
unique Agent ID.
|
||||
</p>
|
||||
|
||||
{error && <ErrorAlert message={error} />}
|
||||
|
||||
{agentId ? (
|
||||
<div className="mb-6 rounded-xl border border-green-200 bg-green-50 p-6">
|
||||
<p className="mb-1 text-sm font-semibold text-green-700">
|
||||
Agent registered successfully!
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm text-slate-700">
|
||||
Agent ID:{' '}
|
||||
<code className="rounded bg-slate-100 px-1.5 py-0.5 font-mono text-sm">
|
||||
{agentId}
|
||||
</code>
|
||||
</p>
|
||||
<CopyButton text={agentId} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-6">
|
||||
<label
|
||||
htmlFor="agent-name"
|
||||
className="mb-1.5 block text-sm font-medium text-slate-700"
|
||||
>
|
||||
Agent Name
|
||||
</label>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
id="agent-name"
|
||||
type="text"
|
||||
value={agentName}
|
||||
onChange={(e) => onAgentNameChange(e.target.value)}
|
||||
placeholder="e.g. my-summarisation-agent"
|
||||
className="flex-1 rounded-lg border border-slate-300 px-4 py-2.5 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-200"
|
||||
/>
|
||||
<button
|
||||
onClick={onRegister}
|
||||
disabled={loading || agentName.trim() === ''}
|
||||
className="rounded-lg bg-brand-600 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-brand-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Registering…' : 'Register Agent'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{agentId && (
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="rounded-lg bg-brand-600 px-6 py-2.5 font-semibold text-white transition-colors hover:bg-brand-700"
|
||||
>
|
||||
Generate Credentials →
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Step3GenerateCredentials({
|
||||
agentId,
|
||||
clientId,
|
||||
clientSecret,
|
||||
loading,
|
||||
error,
|
||||
onGenerate,
|
||||
onNext,
|
||||
}: {
|
||||
agentId: string;
|
||||
clientId: string | null;
|
||||
clientSecret: string | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
onGenerate: () => void;
|
||||
onNext: () => void;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-2 text-2xl font-bold text-slate-900">
|
||||
Step 3: Generate Credentials
|
||||
</h2>
|
||||
<p className="mb-6 text-slate-600">
|
||||
Generate OAuth 2.0 client credentials for agent{' '}
|
||||
<code className="rounded bg-slate-100 px-1.5 py-0.5 text-sm">
|
||||
{agentId}
|
||||
</code>
|
||||
. Store your client secret securely — it will not be shown again.
|
||||
</p>
|
||||
|
||||
{error && <ErrorAlert message={error} />}
|
||||
|
||||
{clientId && clientSecret ? (
|
||||
<div className="mb-6 space-y-4">
|
||||
<div className="rounded-xl border border-brand-200 bg-brand-50 p-5">
|
||||
<p className="mb-3 text-sm font-semibold text-brand-700">
|
||||
Credentials generated. Store these securely!
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
Client ID
|
||||
</p>
|
||||
<div className="flex items-center gap-2 rounded-lg bg-white px-3 py-2 shadow-sm">
|
||||
<code className="flex-1 break-all font-mono text-sm text-slate-800">
|
||||
{clientId}
|
||||
</code>
|
||||
<CopyButton text={clientId} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||
Client Secret
|
||||
</p>
|
||||
<div className="flex items-center gap-2 rounded-lg bg-white px-3 py-2 shadow-sm">
|
||||
<code className="flex-1 break-all font-mono text-sm text-slate-800">
|
||||
{clientSecret}
|
||||
</code>
|
||||
<CopyButton text={clientSecret} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={onGenerate}
|
||||
disabled={loading}
|
||||
className="mb-6 rounded-lg bg-brand-600 px-6 py-2.5 font-semibold text-white transition-colors hover:bg-brand-700 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Generating…' : 'Generate Credentials'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{clientId && clientSecret && (
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="rounded-lg bg-brand-600 px-6 py-2.5 font-semibold text-white transition-colors hover:bg-brand-700"
|
||||
>
|
||||
Choose Your SDK →
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SDK_OPTIONS: { id: Sdk; label: string; description: string }[] = [
|
||||
{
|
||||
id: 'nodejs',
|
||||
label: 'Node.js / TypeScript',
|
||||
description: 'npm install @sentryagent/idp-sdk',
|
||||
},
|
||||
{
|
||||
id: 'python',
|
||||
label: 'Python',
|
||||
description: 'pip install sentryagent-idp',
|
||||
},
|
||||
{
|
||||
id: 'go',
|
||||
label: 'Go',
|
||||
description: 'go get github.com/sentryagent/idp-sdk-go',
|
||||
},
|
||||
{
|
||||
id: 'java',
|
||||
label: 'Java',
|
||||
description: 'Maven / Gradle — ai.sentryagent:idp-sdk:1.0.0',
|
||||
},
|
||||
];
|
||||
|
||||
function Step4SdkSelection({
|
||||
selectedSdk,
|
||||
onSdkChange,
|
||||
clientId,
|
||||
clientSecret,
|
||||
apiUrl,
|
||||
}: {
|
||||
selectedSdk: Sdk;
|
||||
onSdkChange: (sdk: Sdk) => void;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
apiUrl: string;
|
||||
}): React.ReactElement {
|
||||
const snippet = buildSnippet(selectedSdk, apiUrl, clientId, clientSecret);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="mb-2 text-2xl font-bold text-slate-900">
|
||||
Step 4: Choose Your SDK
|
||||
</h2>
|
||||
<p className="mb-6 text-slate-600">
|
||||
Select your language and copy the ready-to-run code snippet below. Your
|
||||
credentials are pre-filled.
|
||||
</p>
|
||||
|
||||
<div className="mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
{SDK_OPTIONS.map(({ id, label, description }) => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => onSdkChange(id)}
|
||||
className={[
|
||||
'rounded-xl border p-4 text-left transition-all',
|
||||
selectedSdk === id
|
||||
? 'border-brand-500 bg-brand-50 shadow-md'
|
||||
: 'border-slate-200 bg-white hover:border-brand-300 hover:bg-brand-50',
|
||||
].join(' ')}
|
||||
>
|
||||
<p
|
||||
className={[
|
||||
'mb-1 text-sm font-semibold',
|
||||
selectedSdk === id ? 'text-brand-700' : 'text-slate-800',
|
||||
].join(' ')}
|
||||
>
|
||||
{label}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">{description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="text-sm font-semibold uppercase tracking-wider text-slate-500">
|
||||
Ready-to-run code
|
||||
</p>
|
||||
<CopyButton text={snippet} />
|
||||
</div>
|
||||
<pre className="overflow-x-auto rounded-xl bg-slate-900 px-5 py-5 text-sm leading-relaxed text-slate-100">
|
||||
<code>{snippet}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 rounded-xl border border-green-200 bg-green-50 p-5 text-center">
|
||||
<p className="text-lg font-bold text-green-800">
|
||||
You are all set!
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-green-700">
|
||||
Your agent is registered and you have credentials. Start making
|
||||
authenticated API calls using the snippet above.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main wizard component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface GetStartedWizardProps {
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
export function GetStartedWizard({
|
||||
apiUrl,
|
||||
}: GetStartedWizardProps): React.ReactElement {
|
||||
const [state, setState] = useState<WizardState>({
|
||||
step: 1,
|
||||
agentName: '',
|
||||
agentId: null,
|
||||
clientId: null,
|
||||
clientSecret: null,
|
||||
selectedSdk: 'nodejs',
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const goToStep = (step: WizardState['step']): void => {
|
||||
setState((prev) => ({ ...prev, step, error: null }));
|
||||
};
|
||||
|
||||
const handleAgentNameChange = (value: string): void => {
|
||||
setState((prev) => ({ ...prev, agentName: value }));
|
||||
};
|
||||
|
||||
const handleSdkChange = (sdk: Sdk): void => {
|
||||
setState((prev) => ({ ...prev, selectedSdk: sdk }));
|
||||
};
|
||||
|
||||
const handleRegisterAgent = async (): Promise<void> => {
|
||||
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/agents`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: state.agentName.trim() }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = (await response.json()) as { message?: string };
|
||||
throw new Error(body.message ?? `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as AgentCreateResponse;
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
agentId: data.agentId,
|
||||
loading: false,
|
||||
error: null,
|
||||
}));
|
||||
} catch (err) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: err instanceof Error ? err.message : 'Failed to register agent',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateCredentials = async (): Promise<void> => {
|
||||
if (!state.agentId) return;
|
||||
|
||||
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${apiUrl}/agents/${state.agentId}/credentials`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const body = (await response.json()) as { message?: string };
|
||||
throw new Error(body.message ?? `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as CredentialsResponse;
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
clientId: data.clientId,
|
||||
clientSecret: data.clientSecret,
|
||||
loading: false,
|
||||
error: null,
|
||||
}));
|
||||
} catch (err) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error:
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: 'Failed to generate credentials',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const stepLabels = [
|
||||
'Account Setup',
|
||||
'Register Agent',
|
||||
'Generate Credentials',
|
||||
'Choose SDK',
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl">
|
||||
{/* Step label row */}
|
||||
<div className="mb-2 flex gap-2 text-xs font-medium text-slate-500">
|
||||
{stepLabels.map((label, i) => (
|
||||
<span
|
||||
key={label}
|
||||
className={[
|
||||
'flex-1 text-center',
|
||||
i + 1 === state.step ? 'font-bold text-brand-700' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<StepIndicator current={state.step} total={4} />
|
||||
|
||||
<div className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
|
||||
{state.step === 1 && (
|
||||
<Step1AccountSetup onNext={() => goToStep(2)} />
|
||||
)}
|
||||
|
||||
{state.step === 2 && (
|
||||
<Step2RegisterAgent
|
||||
agentName={state.agentName}
|
||||
onAgentNameChange={handleAgentNameChange}
|
||||
agentId={state.agentId}
|
||||
loading={state.loading}
|
||||
error={state.error}
|
||||
onRegister={() => void handleRegisterAgent()}
|
||||
onNext={() => goToStep(3)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{state.step === 3 && state.agentId && (
|
||||
<Step3GenerateCredentials
|
||||
agentId={state.agentId}
|
||||
clientId={state.clientId}
|
||||
clientSecret={state.clientSecret}
|
||||
loading={state.loading}
|
||||
error={state.error}
|
||||
onGenerate={() => void handleGenerateCredentials()}
|
||||
onNext={() => goToStep(4)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{state.step === 4 &&
|
||||
state.clientId &&
|
||||
state.clientSecret && (
|
||||
<Step4SdkSelection
|
||||
selectedSdk={state.selectedSdk}
|
||||
onSdkChange={handleSdkChange}
|
||||
clientId={state.clientId}
|
||||
clientSecret={state.clientSecret}
|
||||
apiUrl={apiUrl}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user