fix(skills): refactor skills section to use design system tokens
- Replace hardcoded purple/blue colors with primary/10 backgrounds - Use text-primary for icons instead of hardcoded colors - Use error tokens for error states instead of red-* - Use ProgressBar component instead of custom div progress bars - Use EmptyState component for empty activity feed - Use Card components consistently with proper padding - Fix TypeScript types in integration tests
This commit is contained in:
parent
234548c6b2
commit
9f0d7a2b17
5 changed files with 102 additions and 92 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
|
||||
import { EmptyState } from '@tpmjs/ui/EmptyState/EmptyState';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { Skeleton } from '@tpmjs/ui/Skeleton/Skeleton';
|
||||
|
||||
|
|
@ -88,9 +89,12 @@ export function SkillsActivityFeed({
|
|||
|
||||
if (error) {
|
||||
return (
|
||||
<Card variant="default" className="border-red-200 bg-red-50">
|
||||
<Card variant="default" className="border-error/20 bg-error/5">
|
||||
<CardContent padding="md">
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon icon="alertCircle" size="sm" className="text-error" />
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
@ -99,11 +103,13 @@ export function SkillsActivityFeed({
|
|||
if (questions.length === 0) {
|
||||
return (
|
||||
<Card variant="default" className="border-dashed">
|
||||
<CardContent padding="lg" className="text-center">
|
||||
<Icon icon="message" size="lg" className="mx-auto text-foreground-tertiary mb-2" />
|
||||
<p className="text-foreground-secondary text-sm">
|
||||
No questions yet. Be the first to ask!
|
||||
</p>
|
||||
<CardContent padding="lg">
|
||||
<EmptyState
|
||||
icon="message"
|
||||
title="No questions yet"
|
||||
description="Be the first to ask a question about this collection's tools."
|
||||
size="sm"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
@ -112,7 +118,7 @@ export function SkillsActivityFeed({
|
|||
return (
|
||||
<div className="space-y-3">
|
||||
{questions.map((q) => (
|
||||
<Card key={q.id} variant="default" className="hover:border-foreground/20 transition-colors">
|
||||
<Card key={q.id} variant="default" className="hover:border-primary/20 transition-colors">
|
||||
<CardHeader padding="sm" className="pb-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<CardTitle as="h4" className="text-sm font-medium line-clamp-2">
|
||||
|
|
@ -127,13 +133,13 @@ export function SkillsActivityFeed({
|
|||
</CardHeader>
|
||||
|
||||
<CardContent padding="sm" className="pt-0">
|
||||
<CardDescription className="line-clamp-2 text-xs mb-2">
|
||||
<CardDescription className="line-clamp-2 text-xs mb-3">
|
||||
{q.answer.slice(0, 150)}
|
||||
{q.answer.length > 150 ? '...' : ''}
|
||||
</CardDescription>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<div className="flex gap-1.5 flex-wrap">
|
||||
{q.skillNodes.slice(0, 2).map((sn, i) => (
|
||||
<Badge key={i} variant="outline" size="sm">
|
||||
{sn.skill.name}
|
||||
|
|
@ -145,11 +151,11 @@ export function SkillsActivityFeed({
|
|||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-foreground-tertiary">
|
||||
<div className="flex items-center gap-3 text-xs text-foreground-tertiary">
|
||||
{q.similarCount > 0 && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Icon icon="user" size="sm" />
|
||||
{q.similarCount} similar
|
||||
{q.similarCount}
|
||||
</span>
|
||||
)}
|
||||
<span>{formatRelativeTime(new Date(q.createdAt))}</span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Card, CardContent } from '@tpmjs/ui/Card/Card';
|
||||
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import Link from 'next/link';
|
||||
|
|
@ -52,29 +53,29 @@ curl -X POST "${skillsUrl}" \\
|
|||
}`;
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<section className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-1.5 bg-purple-100 rounded-lg">
|
||||
<Icon icon="star" className="w-4 h-4 text-purple-600" />
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-none bg-primary/10 flex items-center justify-center">
|
||||
<Icon icon="star" className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Skills</h2>
|
||||
<p className="text-xs text-foreground-tertiary">
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Proven in the wild — not declared on paper
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => setShowApiDocs(!showApiDocs)}>
|
||||
<Icon icon="terminal" className="w-4 h-4 mr-1" />
|
||||
<Icon icon="terminal" className="w-4 h-4 mr-1.5" />
|
||||
API
|
||||
</Button>
|
||||
<Link href={`/${username}/collections/${slug}/skills`}>
|
||||
<Button variant="secondary" size="sm">
|
||||
<Icon icon="externalLink" className="w-4 h-4 mr-1" />
|
||||
Full Docs
|
||||
<Icon icon="externalLink" className="w-4 h-4 mr-1.5" />
|
||||
Docs
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -82,34 +83,36 @@ curl -X POST "${skillsUrl}" \\
|
|||
|
||||
{/* API Documentation Toggle */}
|
||||
{showApiDocs && (
|
||||
<div className="p-4 bg-surface border border-border rounded-lg space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">API Endpoint</h4>
|
||||
<div className="px-3 py-2 bg-background border border-border rounded-lg font-mono text-sm text-foreground-secondary overflow-x-auto">
|
||||
POST {skillsUrl}
|
||||
<Card variant="default">
|
||||
<CardContent padding="lg" className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">API Endpoint</h4>
|
||||
<div className="px-3 py-2 bg-muted border border-border font-mono text-sm text-foreground-secondary overflow-x-auto">
|
||||
POST {skillsUrl}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Example Request</h4>
|
||||
<CodeBlock language="bash" code={apiExample} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">Example Request</h4>
|
||||
<CodeBlock language="bash" code={apiExample} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">Example Response</h4>
|
||||
<CodeBlock language="json" code={responseExample} />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-foreground mb-2">Example Response</h4>
|
||||
<CodeBlock language="json" code={responseExample} />
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-foreground-tertiary">
|
||||
<Link href="/docs/skills" className="text-primary hover:underline">
|
||||
View full API documentation
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-border">
|
||||
<Link href="/docs/skills" className="text-sm text-primary hover:underline">
|
||||
View full API documentation →
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{/* Stats Column */}
|
||||
<div className="md:col-span-1">
|
||||
<SkillsStats collectionId={collectionId} />
|
||||
|
|
@ -117,32 +120,34 @@ curl -X POST "${skillsUrl}" \\
|
|||
|
||||
{/* Activity Feed Column */}
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-medium text-foreground-secondary">Recent Questions</h3>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-foreground">Recent Questions</h3>
|
||||
</div>
|
||||
<SkillsActivityFeed collectionId={collectionId} limit={5} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA for empty state */}
|
||||
<div className="p-4 bg-gradient-to-r from-purple-50 to-blue-50 border border-purple-100 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-white rounded-lg shadow-sm">
|
||||
<Icon icon="message" className="w-5 h-5 text-purple-600" />
|
||||
{/* CTA */}
|
||||
<Card variant="default" className="border-primary/20 bg-primary/5">
|
||||
<CardContent padding="md">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-10 h-10 rounded-none bg-primary/10 flex items-center justify-center flex-shrink-0">
|
||||
<Icon icon="message" className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Ask questions to build the skill graph
|
||||
</p>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Every question helps improve future responses for all agents.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="default" size="sm" onClick={() => setShowApiDocs(!showApiDocs)}>
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Ask questions to build the skill graph
|
||||
</p>
|
||||
<p className="text-xs text-foreground-secondary">
|
||||
Every question helps improve future responses for all agents.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="default" size="sm" onClick={() => setShowApiDocs(!showApiDocs)}>
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { Badge } from '@tpmjs/ui/Badge/Badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
|
||||
import { Skeleton } from '@tpmjs/ui/Skeleton/Skeleton';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElem
|
|||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{[1, 2].map((i) => (
|
||||
<Card key={i} variant="default">
|
||||
<CardContent padding="md">
|
||||
|
|
@ -61,9 +62,12 @@ export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElem
|
|||
|
||||
if (error) {
|
||||
return (
|
||||
<Card variant="default" className="border-red-200 bg-red-50">
|
||||
<Card variant="default" className="border-error/20 bg-error/5">
|
||||
<CardContent padding="md">
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon icon="alertCircle" size="sm" className="text-error" />
|
||||
<p className="text-sm text-error">{error}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
@ -76,15 +80,15 @@ export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElem
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Summary Stats */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Card variant="default">
|
||||
<CardContent padding="md">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-blue-50">
|
||||
<Icon icon="message" size="md" className="text-blue-600" />
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-none bg-primary/10 flex items-center justify-center">
|
||||
<Icon icon="message" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-semibold">{stats.totalQuestions}</p>
|
||||
<p className="text-2xl font-semibold text-foreground">{stats.totalQuestions}</p>
|
||||
<p className="text-xs text-foreground-secondary">Questions</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -93,12 +97,12 @@ export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElem
|
|||
|
||||
<Card variant="default">
|
||||
<CardContent padding="md">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-purple-50">
|
||||
<Icon icon="star" size="md" className="text-purple-600" />
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-none bg-primary/10 flex items-center justify-center">
|
||||
<Icon icon="star" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-semibold">{stats.totalSkills}</p>
|
||||
<p className="text-2xl font-semibold text-foreground">{stats.totalSkills}</p>
|
||||
<p className="text-xs text-foreground-secondary">Skills</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -115,26 +119,21 @@ export function SkillsStats({ collectionId }: SkillsStatsProps): React.ReactElem
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent padding="sm" className="pt-0">
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{stats.topSkills.slice(0, 5).map((skill, i) => (
|
||||
<div key={i} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Badge variant="outline" size="sm" className="truncate max-w-[180px]">
|
||||
<div key={i} className="space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<Badge variant="outline" size="sm" className="truncate max-w-[140px]">
|
||||
{skill.name}
|
||||
</Badge>
|
||||
<span className="text-xs text-foreground-secondary">{skill.questionCount} Q</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-foreground-secondary flex-shrink-0">
|
||||
<span>{skill.questionCount} Q</span>
|
||||
<div
|
||||
className="w-12 h-1.5 bg-gray-200 rounded-full overflow-hidden"
|
||||
title={`${Math.round(skill.confidence * 100)}% confidence`}
|
||||
>
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full"
|
||||
style={{ width: `${skill.confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={skill.confidence * 100}
|
||||
size="sm"
|
||||
variant="primary"
|
||||
showLabel={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ async function isServerAvailable(): Promise<boolean> {
|
|||
|
||||
describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Scenarios CRUD Integration', () => {
|
||||
let serverAvailable = false;
|
||||
let testCollection: { id: string; slug: string; userId: string } | null = null;
|
||||
const testScenarioId: string | null = null;
|
||||
let testCollection: { id: string; slug: string | null; userId: string } | null = null;
|
||||
let testScenarioId: string | null = null;
|
||||
|
||||
beforeAll(async () => {
|
||||
serverAvailable = await isServerAvailable();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ async function isServerAvailable(): Promise<boolean> {
|
|||
|
||||
describe.skipIf(!INTEGRATION_TESTS_ENABLED)('Scenario Execution Integration', () => {
|
||||
let serverAvailable = false;
|
||||
let testScenario: { id: string; name: string; collectionId: string } | null = null;
|
||||
let testScenario: { id: string; name: string | null; collectionId: string | null } | null = null;
|
||||
|
||||
beforeAll(async () => {
|
||||
serverAvailable = await isServerAvailable();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue