@vertesia/tools-admin-ui 1.0.0-dev.20260227.112605Z → 1.0.0-dev.20260305.083323Z

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/lib/AdminApp.d.ts +3 -1
  2. package/lib/AdminApp.d.ts.map +1 -1
  3. package/lib/components/AdminTopBar.d.ts +6 -0
  4. package/lib/components/AdminTopBar.d.ts.map +1 -0
  5. package/lib/components/CollectionCard.d.ts.map +1 -1
  6. package/lib/components/DetailPage.d.ts.map +1 -1
  7. package/lib/components/EndpointPanel.d.ts.map +1 -1
  8. package/lib/components/HeroSection.d.ts.map +1 -1
  9. package/lib/components/ResourceCard.d.ts.map +1 -1
  10. package/lib/components/ResourceSection.d.ts.map +1 -1
  11. package/lib/components/SearchBar.d.ts.map +1 -1
  12. package/lib/components/SummaryBadge.d.ts.map +1 -1
  13. package/lib/components/index.d.ts +6 -5
  14. package/lib/components/index.d.ts.map +1 -1
  15. package/lib/components/typeVariants.d.ts +5 -0
  16. package/lib/components/typeVariants.d.ts.map +1 -0
  17. package/lib/hooks.d.ts +1 -1
  18. package/lib/hooks.d.ts.map +1 -1
  19. package/lib/pages/HomePage.d.ts.map +1 -1
  20. package/lib/pages/InteractionCollection.d.ts.map +1 -1
  21. package/lib/pages/InteractionDetail.d.ts.map +1 -1
  22. package/lib/pages/SkillCollection.d.ts.map +1 -1
  23. package/lib/pages/SkillDetail.d.ts.map +1 -1
  24. package/lib/pages/TemplateCollection.d.ts.map +1 -1
  25. package/lib/pages/TemplateDetail.d.ts.map +1 -1
  26. package/lib/pages/ToolCollection.d.ts.map +1 -1
  27. package/lib/pages/TypeCollection.d.ts.map +1 -1
  28. package/lib/pages/TypeDetail.d.ts.map +1 -1
  29. package/lib/tools-admin-ui.js +410 -377
  30. package/lib/tools-admin-ui.js.map +1 -1
  31. package/package.json +8 -4
  32. package/src/AdminApp.tsx +21 -17
  33. package/src/components/AdminTopBar.tsx +39 -0
  34. package/src/components/CollectionCard.tsx +18 -13
  35. package/src/components/DetailPage.tsx +20 -11
  36. package/src/components/EndpointPanel.tsx +16 -7
  37. package/src/components/HeroSection.tsx +51 -45
  38. package/src/components/ResourceCard.tsx +23 -18
  39. package/src/components/ResourceSection.tsx +7 -5
  40. package/src/components/SearchBar.tsx +8 -6
  41. package/src/components/SummaryBadge.tsx +4 -3
  42. package/src/components/index.ts +6 -5
  43. package/src/components/typeVariants.ts +18 -0
  44. package/src/dev/index.css +13 -0
  45. package/src/dev/main.tsx +5 -2
  46. package/src/hooks.ts +2 -1
  47. package/src/pages/HomePage.tsx +18 -15
  48. package/src/pages/InteractionCollection.tsx +25 -27
  49. package/src/pages/InteractionDetail.tsx +35 -34
  50. package/src/pages/SkillCollection.tsx +29 -32
  51. package/src/pages/SkillDetail.tsx +31 -41
  52. package/src/pages/TemplateCollection.tsx +25 -21
  53. package/src/pages/TemplateDetail.tsx +18 -17
  54. package/src/pages/ToolCollection.tsx +22 -16
  55. package/src/pages/TypeCollection.tsx +32 -24
  56. package/src/pages/TypeDetail.tsx +16 -18
  57. package/src/theme.css +12 -0
  58. package/src/admin.css +0 -650
@@ -1,14 +1,16 @@
1
- import { useFetch } from '@vertesia/ui/core';
2
- import { useParams, NavLink } from '@vertesia/ui/router';
3
1
  import type { CatalogInteractionRef } from '@vertesia/common';
2
+ import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
3
+ import { NavLink, useParams } from '@vertesia/ui/router';
4
+
4
5
  import { useAdminContext } from '../AdminContext.js';
5
6
  import { DetailPage } from '../components/DetailPage.js';
7
+ import { TYPE_VARIANTS } from '../components/typeVariants.js';
6
8
 
7
9
  export function InteractionCollection() {
8
10
  const collection = useParams('collection');
9
11
  const { baseUrl } = useAdminContext();
10
12
 
11
- const { data: interactions, isLoading, error } = useFetch<CatalogInteractionRef[]>(
13
+ const { data: interactions, error } = useFetch<CatalogInteractionRef[]>(
12
14
  () => fetch(`${baseUrl}/interactions/${collection}`).then(r => {
13
15
  if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
14
16
  return r.json();
@@ -16,12 +18,12 @@ export function InteractionCollection() {
16
18
  [baseUrl, collection]
17
19
  );
18
20
 
19
- if (isLoading) {
20
- return <div className="vta-loading">Loading collection...</div>;
21
+ if (error) {
22
+ return <div className="p-6 text-destructive">Failed to load collection &ldquo;{collection}&rdquo;.</div>;
21
23
  }
22
24
 
23
- if (error || !interactions) {
24
- return <div className="vta-error">Failed to load collection &ldquo;{collection}&rdquo;.</div>;
25
+ if (!interactions) {
26
+ return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
25
27
  }
26
28
 
27
29
  return (
@@ -30,27 +32,23 @@ export function InteractionCollection() {
30
32
  title={collection}
31
33
  description={`${interactions.length} interaction${interactions.length !== 1 ? 's' : ''} in this collection.`}
32
34
  >
33
- <div className="vta-card-grid">
35
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
34
36
  {interactions.map(inter => (
35
- <NavLink
36
- key={inter.id}
37
- href={`/interactions/${collection}/${inter.name}`}
38
- className="vta-card-link"
39
- >
40
- <div className="vta-card vta-card--link">
41
- <span className="vta-card-type vta-card-type--interaction">interaction</span>
42
- <div className="vta-card-title">{inter.title || inter.name}</div>
43
- <div className="vta-card-desc">
44
- {inter.description || 'No description'}
45
- </div>
46
- {inter.tags && inter.tags.length > 0 && (
47
- <div className="vta-card-tags">
48
- {inter.tags.map(tag => (
49
- <span key={tag} className="vta-tag">{tag}</span>
50
- ))}
51
- </div>
52
- )}
53
- </div>
37
+ <NavLink key={inter.id} href={`/interactions/${collection}/${inter.name}`} className="block no-underline">
38
+ <Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
39
+ <CardContent className="p-5">
40
+ <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.interaction}`}>
41
+ interaction
42
+ </span>
43
+ <div className="font-semibold text-card-foreground">{inter.title || inter.name}</div>
44
+ <div className="mt-1 text-sm text-muted-foreground">{inter.description || 'No description'}</div>
45
+ {inter.tags && inter.tags.length > 0 && (
46
+ <div className="mt-2 flex flex-wrap gap-1">
47
+ {inter.tags.map(tag => <Badge key={tag}>{tag}</Badge>)}
48
+ </div>
49
+ )}
50
+ </CardContent>
51
+ </Card>
54
52
  </NavLink>
55
53
  ))}
56
54
  </div>
@@ -1,9 +1,11 @@
1
- import { useFetch } from '@vertesia/ui/core';
2
- import { useParams } from '@vertesia/ui/router';
3
1
  import type { InteractionSpec } from '@vertesia/common';
2
+ import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
3
+ import { useParams } from '@vertesia/ui/router';
4
+ import { useUserSession } from '@vertesia/ui/session';
5
+
4
6
  import { useAdminContext } from '../AdminContext.js';
5
7
  import { DetailPage } from '../components/DetailPage.js';
6
- import { useUserSession } from '@vertesia/ui/session';
8
+ import { ROLE_VARIANTS } from '../components/typeVariants.js';
7
9
 
8
10
  type InteractionResponse = InteractionSpec & { id: string };
9
11
 
@@ -14,7 +16,7 @@ export function InteractionDetail() {
14
16
  const name = params.name;
15
17
  const { baseUrl } = useAdminContext();
16
18
 
17
- const { data: interaction, isLoading, error } = useFetch<InteractionResponse>(
19
+ const { data: interaction, error } = useFetch<InteractionResponse>(
18
20
  () => client.getRawJWT().then(token => fetch(`${baseUrl}/interactions/${collection}/${name}`, {
19
21
  headers: {
20
22
  Authorization: `Bearer ${token}`,
@@ -26,12 +28,12 @@ export function InteractionDetail() {
26
28
  [baseUrl, collection, name]
27
29
  );
28
30
 
29
- if (isLoading) {
30
- return <div className="vta-loading">Loading interaction...</div>;
31
+ if (error) {
32
+ return <div className="p-6 text-destructive">Failed to load interaction &ldquo;{name}&rdquo;.</div>;
31
33
  }
32
34
 
33
- if (error || !interaction) {
34
- return <div className="vta-error">Failed to load interaction &ldquo;{name}&rdquo;.</div>;
35
+ if (!interaction) {
36
+ return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
35
37
  }
36
38
 
37
39
  const { agent_runner_options } = interaction;
@@ -46,44 +48,43 @@ export function InteractionDetail() {
46
48
  tags={interaction.tags}
47
49
  backHref={`/interactions/${collection}`}
48
50
  >
49
- {/* Prompts */}
50
51
  {interaction.prompts && interaction.prompts.length > 0 && (
51
- <div className="vta-detail-section">
52
- <h2>Prompts</h2>
53
- {interaction.prompts.map((prompt, i) => (
54
- <div key={i} className="vta-detail-card">
55
- <div className="vta-detail-card-header">
56
- <span className={`vta-detail-role vta-detail-role--${prompt.role}`}>
57
- {prompt.role}
58
- </span>
59
- {prompt.name && (
60
- <span className="vta-detail-prompt-name">{prompt.name}</span>
61
- )}
62
- </div>
63
- <pre className="vta-detail-code">{prompt.content}</pre>
64
- </div>
52
+ <div className="mb-8">
53
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Prompts</h2>
54
+ {interaction.prompts.map((prompt) => (
55
+ <Card key={`${prompt.role}-${prompt.name ?? ''}`} className="mb-3">
56
+ <CardContent className="p-4">
57
+ <div className="mb-2 flex items-center gap-2">
58
+ <span className={`rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${ROLE_VARIANTS[prompt.role] ?? ''}`}>
59
+ {prompt.role}
60
+ </span>
61
+ {prompt.name && (
62
+ <span className="text-sm italic text-muted-foreground">{prompt.name}</span>
63
+ )}
64
+ </div>
65
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">{prompt.content}</pre>
66
+ </CardContent>
67
+ </Card>
65
68
  ))}
66
69
  </div>
67
70
  )}
68
71
 
69
- {/* Result schema */}
70
72
  {interaction.result_schema && (
71
- <div className="vta-detail-section">
72
- <h2>Result Schema</h2>
73
- <pre className="vta-detail-code">
73
+ <div className="mb-8">
74
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Result Schema</h2>
75
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
74
76
  {JSON.stringify(interaction.result_schema, null, 2)}
75
77
  </pre>
76
78
  </div>
77
79
  )}
78
80
 
79
- {/* Agent runner options */}
80
81
  {hasAgentFlags && (
81
- <div className="vta-detail-section">
82
- <h2>Agent Runner</h2>
83
- <div className="vta-detail-flags">
84
- {agent_runner_options.is_agent && <span className="vta-detail-flag">Agent</span>}
85
- {agent_runner_options.is_tool && <span className="vta-detail-flag">Tool</span>}
86
- {agent_runner_options.is_skill && <span className="vta-detail-flag">Skill</span>}
82
+ <div className="mb-8">
83
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Agent Runner</h2>
84
+ <div className="flex flex-wrap gap-2">
85
+ {agent_runner_options.is_agent && <Badge variant="success">Agent</Badge>}
86
+ {agent_runner_options.is_tool && <Badge variant="success">Tool</Badge>}
87
+ {agent_runner_options.is_skill && <Badge variant="success">Skill</Badge>}
87
88
  </div>
88
89
  </div>
89
90
  )}
@@ -1,8 +1,10 @@
1
+ import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
2
+ import { NavLink, useParams } from '@vertesia/ui/router';
1
3
  import { useMemo } from 'react';
2
- import { useFetch } from '@vertesia/ui/core';
3
- import { useParams, NavLink } from '@vertesia/ui/router';
4
+
4
5
  import { useAdminContext } from '../AdminContext.js';
5
6
  import { DetailPage } from '../components/DetailPage.js';
7
+ import { TYPE_VARIANTS } from '../components/typeVariants.js';
6
8
 
7
9
  interface SkillToolDef {
8
10
  name: string;
@@ -35,7 +37,7 @@ export function SkillCollection() {
35
37
  const collection = useParams('collection');
36
38
  const { baseUrl } = useAdminContext();
37
39
 
38
- const { data, isLoading, error } = useFetch<SkillCollectionResponse>(
40
+ const { data, error } = useFetch<SkillCollectionResponse>(
39
41
  () => fetch(`${baseUrl}/skills/${collection}`).then(r => {
40
42
  if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
41
43
  return r.json();
@@ -55,8 +57,8 @@ export function SkillCollection() {
55
57
  .map(([name, w]) => ({ name, ...w }));
56
58
  }, [widgetsData, collection]);
57
59
 
58
- if (isLoading) return <div className="vta-loading">Loading collection...</div>;
59
- if (error || !data) return <div className="vta-error">Failed to load skill collection &ldquo;{collection}&rdquo;.</div>;
60
+ if (error) return <div className="p-6 text-destructive">Failed to load skill collection &ldquo;{collection}&rdquo;.</div>;
61
+ if (!data) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
60
62
 
61
63
  return (
62
64
  <DetailPage
@@ -64,44 +66,39 @@ export function SkillCollection() {
64
66
  title={data.title || collection}
65
67
  description={data.description || `${data.tools.length} skill${data.tools.length !== 1 ? 's' : ''} in this collection.`}
66
68
  >
67
- {/* Widgets provided by this collection */}
68
69
  {collectionWidgets.length > 0 && (
69
- <div className="vta-detail-section">
70
- <h2>Widgets</h2>
71
- <div className="vta-detail-flags">
70
+ <div className="mb-8">
71
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
72
+ <div className="flex flex-wrap gap-2">
72
73
  {collectionWidgets.map(w => (
73
- <span key={w.name} className="vta-detail-flag">
74
+ <Badge key={w.name} variant="success">
74
75
  {w.name}
75
- <span className="vta-card-url" style={{ marginLeft: '0.5rem' }}>
76
- (skill: {w.skill})
77
- </span>
78
- </span>
76
+ <span className="ml-2 font-mono text-xs opacity-70">(skill: {w.skill})</span>
77
+ </Badge>
79
78
  ))}
80
79
  </div>
81
80
  </div>
82
81
  )}
83
82
 
84
- <div className="vta-card-grid">
83
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
85
84
  {data.tools.map(skill => {
86
85
  const displayName = skillDisplayName(skill.name);
87
86
  return (
88
- <NavLink
89
- key={skill.name}
90
- href={`/skills/${collection}/${displayName}`}
91
- className="vta-card-link"
92
- >
93
- <div className="vta-card vta-card--link">
94
- <span className="vta-card-type vta-card-type--skill">skill</span>
95
- <div className="vta-card-title">{displayName}</div>
96
- <div className="vta-card-desc">{skill.description || 'No description'}</div>
97
- {skill.related_tools && skill.related_tools.length > 0 && (
98
- <div className="vta-card-tags">
99
- {skill.related_tools.map(t => (
100
- <span key={t} className="vta-tag">{t}</span>
101
- ))}
102
- </div>
103
- )}
104
- </div>
87
+ <NavLink key={skill.name} href={`/skills/${collection}/${displayName}`} className="block no-underline">
88
+ <Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
89
+ <CardContent className="p-5">
90
+ <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.skill}`}>
91
+ skill
92
+ </span>
93
+ <div className="font-semibold text-card-foreground">{displayName}</div>
94
+ <div className="mt-1 text-sm text-muted-foreground">{skill.description || 'No description'}</div>
95
+ {skill.related_tools && skill.related_tools.length > 0 && (
96
+ <div className="mt-2 flex flex-wrap gap-1">
97
+ {skill.related_tools.map(t => <Badge key={t}>{t}</Badge>)}
98
+ </div>
99
+ )}
100
+ </CardContent>
101
+ </Card>
105
102
  </NavLink>
106
103
  );
107
104
  })}
@@ -1,5 +1,6 @@
1
- import { useFetch } from '@vertesia/ui/core';
1
+ import { Badge, Spinner, useFetch } from '@vertesia/ui/core';
2
2
  import { useParams } from '@vertesia/ui/router';
3
+
3
4
  import { useAdminContext } from '../AdminContext.js';
4
5
  import { DetailPage } from '../components/DetailPage.js';
5
6
 
@@ -25,7 +26,7 @@ export function SkillDetail() {
25
26
  const name = params.name;
26
27
  const { baseUrl } = useAdminContext();
27
28
 
28
- const { data: skill, isLoading, error } = useFetch<SkillDefinitionResponse>(
29
+ const { data: skill, error } = useFetch<SkillDefinitionResponse>(
29
30
  () => fetch(`${baseUrl}/skills/${collection}/${name}`).then(r => {
30
31
  if (!r.ok) throw new Error(`Failed to load skill: ${r.statusText}`);
31
32
  return r.json();
@@ -33,8 +34,8 @@ export function SkillDetail() {
33
34
  [baseUrl, collection, name]
34
35
  );
35
36
 
36
- if (isLoading) return <div className="vta-loading">Loading skill...</div>;
37
- if (error || !skill) return <div className="vta-error">Failed to load skill &ldquo;{name}&rdquo;.</div>;
37
+ if (error) return <div className="p-6 text-destructive">Failed to load skill &ldquo;{name}&rdquo;.</div>;
38
+ if (!skill) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
38
39
 
39
40
  return (
40
41
  <DetailPage
@@ -43,66 +44,55 @@ export function SkillDetail() {
43
44
  description={skill.description}
44
45
  backHref={`/skills/${collection}`}
45
46
  >
46
- {/* Widgets */}
47
47
  {skill.widgets && skill.widgets.length > 0 && (
48
- <div className="vta-detail-section">
49
- <h2>Widgets</h2>
50
- <div className="vta-detail-flags">
51
- {skill.widgets.map(w => (
52
- <span key={w} className="vta-detail-flag">{w}</span>
53
- ))}
48
+ <div className="mb-8">
49
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
50
+ <div className="flex flex-wrap gap-2">
51
+ {skill.widgets.map(w => <Badge key={w} variant="success">{w}</Badge>)}
54
52
  </div>
55
53
  </div>
56
54
  )}
57
55
 
58
- {/* Scripts */}
59
56
  {skill.scripts && skill.scripts.length > 0 && (
60
- <div className="vta-detail-section">
61
- <h2>Scripts</h2>
62
- <div className="vta-detail-flags">
63
- {skill.scripts.map(s => (
64
- <span key={s} className="vta-detail-flag">{s}</span>
65
- ))}
57
+ <div className="mb-8">
58
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Scripts</h2>
59
+ <div className="flex flex-wrap gap-2">
60
+ {skill.scripts.map(s => <Badge key={s} variant="success">{s}</Badge>)}
66
61
  </div>
67
62
  </div>
68
63
  )}
69
64
 
70
- {/* Related tools */}
71
65
  {skill.related_tools && skill.related_tools.length > 0 && (
72
- <div className="vta-detail-section">
73
- <h2>Related Tools</h2>
74
- <div className="vta-detail-flags">
75
- {skill.related_tools.map(t => (
76
- <span key={t} className="vta-detail-flag">{t}</span>
77
- ))}
66
+ <div className="mb-8">
67
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Related Tools</h2>
68
+ <div className="flex flex-wrap gap-2">
69
+ {skill.related_tools.map(t => <Badge key={t} variant="success">{t}</Badge>)}
78
70
  </div>
79
71
  </div>
80
72
  )}
81
73
 
82
- {/* Execution environment */}
83
74
  {skill.execution && (
84
- <div className="vta-detail-section">
85
- <h2>Execution</h2>
86
- <div className="vta-detail-flags">
87
- <span className="vta-detail-flag">{skill.execution.language}</span>
88
- {skill.execution.packages?.map(p => (
89
- <span key={p} className="vta-detail-flag">{p}</span>
90
- ))}
75
+ <div className="mb-8">
76
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Execution</h2>
77
+ <div className="flex flex-wrap gap-2">
78
+ <Badge variant="success">{skill.execution.language}</Badge>
79
+ {skill.execution.packages?.map(p => <Badge key={p} variant="success">{p}</Badge>)}
91
80
  </div>
92
81
  </div>
93
82
  )}
94
83
 
95
- {/* Instructions */}
96
- <div className="vta-detail-section">
97
- <h2>Instructions {skill.content_type === 'jst' && <span className="vta-tag">JST template</span>}</h2>
98
- <pre className="vta-detail-code">{skill.instructions}</pre>
84
+ <div className="mb-8">
85
+ <h2 className="mb-3 text-lg font-semibold text-foreground">
86
+ Instructions
87
+ {skill.content_type === 'jst' && <Badge className="ml-2">JST template</Badge>}
88
+ </h2>
89
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">{skill.instructions}</pre>
99
90
  </div>
100
91
 
101
- {/* Input schema */}
102
92
  {skill.input_schema && (
103
- <div className="vta-detail-section">
104
- <h2>Input Schema</h2>
105
- <pre className="vta-detail-code">
93
+ <div className="mb-8">
94
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Input Schema</h2>
95
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
106
96
  {JSON.stringify(skill.input_schema, null, 2)}
107
97
  </pre>
108
98
  </div>
@@ -1,14 +1,16 @@
1
- import { useFetch } from '@vertesia/ui/core';
2
- import { useParams, NavLink } from '@vertesia/ui/router';
3
1
  import type { RenderingTemplateDefinitionRef } from '@vertesia/common';
2
+ import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
3
+ import { NavLink, useParams } from '@vertesia/ui/router';
4
+
4
5
  import { useAdminContext } from '../AdminContext.js';
5
6
  import { DetailPage } from '../components/DetailPage.js';
7
+ import { TYPE_VARIANTS } from '../components/typeVariants.js';
6
8
 
7
9
  export function TemplateCollection() {
8
10
  const collection = useParams('collection');
9
11
  const { baseUrl } = useAdminContext();
10
12
 
11
- const { data: templates, isLoading, error } = useFetch<RenderingTemplateDefinitionRef[]>(
13
+ const { data: templates, error } = useFetch<RenderingTemplateDefinitionRef[]>(
12
14
  () => fetch(`${baseUrl}/templates/${collection}`).then(r => {
13
15
  if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
14
16
  return r.json();
@@ -16,8 +18,8 @@ export function TemplateCollection() {
16
18
  [baseUrl, collection]
17
19
  );
18
20
 
19
- if (isLoading) return <div className="vta-loading">Loading collection...</div>;
20
- if (error || !templates) return <div className="vta-error">Failed to load template collection &ldquo;{collection}&rdquo;.</div>;
21
+ if (error) return <div className="p-6 text-destructive">Failed to load template collection &ldquo;{collection}&rdquo;.</div>;
22
+ if (!templates) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
21
23
 
22
24
  return (
23
25
  <DetailPage
@@ -25,27 +27,29 @@ export function TemplateCollection() {
25
27
  title={collection}
26
28
  description={`${templates.length} template${templates.length !== 1 ? 's' : ''} in this collection.`}
27
29
  >
28
- <div className="vta-card-grid">
30
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
29
31
  {templates.map(tmpl => (
30
32
  <NavLink
31
33
  key={tmpl.name}
32
34
  href={`/templates/${collection}/${tmpl.name}`}
33
- className="vta-card-link"
35
+ className="no-underline"
34
36
  >
35
- <div className="vta-card vta-card--link">
36
- <div className="vta-card-type vta-card-type--template">
37
- {tmpl.type || 'template'}
38
- </div>
39
- <div className="vta-card-title">{tmpl.title || tmpl.name}</div>
40
- <div className="vta-card-desc">{tmpl.description || 'No description'}</div>
41
- {tmpl.tags && tmpl.tags.length > 0 && (
42
- <div className="vta-card-tags">
43
- {tmpl.tags.map(tag => (
44
- <span key={tag} className="vta-tag">{tag}</span>
45
- ))}
46
- </div>
47
- )}
48
- </div>
37
+ <Card className="h-full transition-all hover:-translate-y-0.5 hover:shadow-md">
38
+ <CardContent className="p-5">
39
+ <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.template}`}>
40
+ {tmpl.type || 'template'}
41
+ </span>
42
+ <div className="font-semibold text-card-foreground">{tmpl.title || tmpl.name}</div>
43
+ <div className="mt-1 text-sm text-muted-foreground">{tmpl.description || 'No description'}</div>
44
+ {tmpl.tags && tmpl.tags.length > 0 && (
45
+ <div className="mt-3 flex flex-wrap gap-1.5">
46
+ {tmpl.tags.map(tag => (
47
+ <Badge key={tag} variant="default">{tag}</Badge>
48
+ ))}
49
+ </div>
50
+ )}
51
+ </CardContent>
52
+ </Card>
49
53
  </NavLink>
50
54
  ))}
51
55
  </div>
@@ -1,5 +1,6 @@
1
- import { useFetch } from '@vertesia/ui/core';
1
+ import { Badge, Spinner, useFetch } from '@vertesia/ui/core';
2
2
  import { useParams } from '@vertesia/ui/router';
3
+
3
4
  import { useAdminContext } from '../AdminContext.js';
4
5
  import { DetailPage } from '../components/DetailPage.js';
5
6
 
@@ -19,7 +20,7 @@ export function TemplateDetail() {
19
20
  const name = params.name;
20
21
  const { baseUrl } = useAdminContext();
21
22
 
22
- const { data: template, isLoading, error } = useFetch<TemplateDefinitionResponse>(
23
+ const { data: template, error } = useFetch<TemplateDefinitionResponse>(
23
24
  () => fetch(`${baseUrl}/templates/${collection}/${name}`).then(r => {
24
25
  if (!r.ok) throw new Error(`Failed to load template: ${r.statusText}`);
25
26
  return r.json();
@@ -27,8 +28,8 @@ export function TemplateDetail() {
27
28
  [baseUrl, collection, name]
28
29
  );
29
30
 
30
- if (isLoading) return <div className="vta-loading">Loading template...</div>;
31
- if (error || !template) return <div className="vta-error">Failed to load template &ldquo;{name}&rdquo;.</div>;
31
+ if (error) return <div className="p-6 text-destructive">Failed to load template &ldquo;{name}&rdquo;.</div>;
32
+ if (!template) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
32
33
 
33
34
  return (
34
35
  <DetailPage
@@ -38,30 +39,30 @@ export function TemplateDetail() {
38
39
  tags={template.tags}
39
40
  backHref={`/templates/${collection}`}
40
41
  >
41
- {/* Type badge & Assets */}
42
- <div className="vta-detail-section">
43
- <div className="vta-detail-flags">
44
- <span className="vta-detail-flag">{template.type}</span>
42
+ <div className="mb-8">
43
+ <div className="flex flex-wrap gap-2">
44
+ <Badge variant="success">{template.type}</Badge>
45
45
  </div>
46
46
  </div>
47
47
 
48
48
  {template.assets && template.assets.length > 0 && (
49
- <div className="vta-detail-section">
50
- <h2>Assets</h2>
51
- <div className="vta-detail-flags">
49
+ <div className="mb-8">
50
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Assets</h2>
51
+ <div className="flex flex-wrap gap-2">
52
52
  {template.assets.map(asset => (
53
- <span key={asset} className="vta-detail-flag">
53
+ <Badge key={asset} variant="success">
54
54
  {asset.split('/').pop()}
55
- </span>
55
+ </Badge>
56
56
  ))}
57
57
  </div>
58
58
  </div>
59
59
  )}
60
60
 
61
- {/* Instructions */}
62
- <div className="vta-detail-section">
63
- <h2>Instructions</h2>
64
- <pre className="vta-detail-code">{template.instructions}</pre>
61
+ <div className="mb-8">
62
+ <h2 className="mb-3 text-lg font-semibold text-foreground">Instructions</h2>
63
+ <pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
64
+ {template.instructions}
65
+ </pre>
65
66
  </div>
66
67
  </DetailPage>
67
68
  );