Skip to content

Commit 0e5d58d

Browse files
committed
feat: extension category as badges
1 parent dab77db commit 0e5d58d

File tree

11 files changed

+139
-9
lines changed

11 files changed

+139
-9
lines changed

api/api/versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"version": "v1",
55
"status": "active",
6-
"release_date": "2025-10-15T21:11:09.336673993+05:30",
6+
"release_date": "2025-10-15T21:35:07.482629937+05:30",
77
"end_of_life": "0001-01-01T00:00:00Z",
88
"changes": [
99
"Initial API version"

api/doc/openapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

api/internal/features/extension/controller/get_extensions.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ func (c *ExtensionsController) GetExtensions(ctx fuego.ContextNoBody) (*types.Ex
6464
return response, nil
6565
}
6666

67+
func (c *ExtensionsController) GetCategories(ctx fuego.ContextNoBody) ([]types.ExtensionCategory, error) {
68+
cats, err := c.service.ListCategories()
69+
if err != nil {
70+
c.logger.Log(logger.Error, err.Error(), "")
71+
return nil, fuego.HTTPError{Err: err, Status: http.StatusInternalServerError}
72+
}
73+
return cats, nil
74+
}
75+
6776
func (c *ExtensionsController) GetExtension(ctx fuego.ContextNoBody) (types.Extension, error) {
6877
id := ctx.PathParam("id")
6978
if id == "" {

api/internal/features/extension/service/service.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ func (s *ExtensionService) ListExtensions(params types.ExtensionListParams) (*ty
185185
return response, nil
186186
}
187187

188+
func (s *ExtensionService) ListCategories() ([]types.ExtensionCategory, error) {
189+
cats, err := s.storage.ListCategories()
190+
if err != nil {
191+
s.logger.Log(logger.Error, err.Error(), "")
192+
return nil, err
193+
}
194+
return cats, nil
195+
}
196+
188197
func (s *ExtensionService) ParseMultipartRunRequest(r *http.Request) (map[string]interface{}, error) {
189198
if err := r.ParseMultipartForm(32 << 20); err != nil {
190199
return nil, err

api/internal/features/extension/storage/storage.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type ExtensionStorageInterface interface {
2323
UpdateExtension(extension *types.Extension) error
2424
DeleteExtension(id string) error
2525
ListExtensions(params types.ExtensionListParams) (*types.ExtensionListResponse, error)
26+
ListCategories() ([]types.ExtensionCategory, error)
2627
CreateExecution(exec *types.ExtensionExecution) error
2728
CreateExecutionSteps(steps []types.ExecutionStep) error
2829
ListExecutionSteps(executionID string) ([]types.ExecutionStep, error)
@@ -224,6 +225,19 @@ func (s *ExtensionStorage) ListExtensions(params types.ExtensionListParams) (*ty
224225
}, nil
225226
}
226227

228+
func (s *ExtensionStorage) ListCategories() ([]types.ExtensionCategory, error) {
229+
var categories []types.ExtensionCategory
230+
err := s.getDB().NewSelect().
231+
TableExpr("extensions").
232+
ColumnExpr("DISTINCT category").
233+
Where("deleted_at IS NULL").
234+
Scan(s.Ctx, &categories)
235+
if err != nil {
236+
return nil, err
237+
}
238+
return categories, nil
239+
}
240+
227241
func (s *ExtensionStorage) CreateExecution(exec *types.ExtensionExecution) error {
228242
_, err := s.getDB().NewInsert().Model(exec).Exec(s.Ctx)
229243
if err != nil {

api/internal/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ func (router *Router) ContainerRoutes(s *fuego.Server, containerController *cont
429429

430430
func (router *Router) ExtensionRoutes(s *fuego.Server, extensionController *extension.ExtensionsController) {
431431
fuego.Get(s, "", extensionController.GetExtensions)
432+
fuego.Get(s, "/categories", extensionController.GetCategories)
432433
fuego.Get(s, "/{id}", extensionController.GetExtension)
433434
fuego.Get(s, "/by-extension-id/{extension_id}", extensionController.GetExtensionByExtensionID)
434435
fuego.Get(s, "/by-extension-id/{extension_id}/executions", extensionController.ListExecutionsByExtensionID)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { Badge } from '@/components/ui/badge';
5+
import { cn } from '@/lib/utils';
6+
7+
type CategoryBadgesProps = {
8+
categories: string[];
9+
selected?: string | null;
10+
onChange?: (value: string | null) => void;
11+
className?: string;
12+
showAll?: boolean;
13+
};
14+
15+
export default function CategoryBadges({
16+
categories,
17+
selected = null,
18+
onChange,
19+
className,
20+
showAll = true
21+
}: CategoryBadgesProps) {
22+
const handleSelect = (value: string | null) => {
23+
onChange?.(value);
24+
};
25+
26+
return (
27+
<div className={cn('w-full', className)}>
28+
<div className="flex flex-wrap items-center gap-2">
29+
{showAll && (
30+
<button
31+
type="button"
32+
onClick={() => handleSelect(null)}
33+
className="focus:outline-none"
34+
>
35+
<Badge
36+
variant={selected === null ? 'default' : 'outline'}
37+
className="px-3 py-1"
38+
>
39+
All
40+
</Badge>
41+
</button>
42+
)}
43+
{categories.map((cat) => (
44+
<button
45+
key={cat}
46+
type="button"
47+
onClick={() => handleSelect(cat === selected ? null : cat)}
48+
className="focus:outline-none"
49+
>
50+
<Badge
51+
variant={selected === cat ? 'default' : 'outline'}
52+
className="px-3 py-1"
53+
>
54+
{cat}
55+
</Badge>
56+
</button>
57+
))}
58+
</div>
59+
</div>
60+
);
61+
}
62+
63+

view/app/extensions/hooks/use-extensions.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import { useState } from 'react';
44
import { useRouter } from 'next/navigation';
5-
import { Extension, ExtensionListParams, SortDirection, ExtensionSortField } from '@/redux/types/extension';
6-
import { useGetExtensionsQuery, useRunExtensionMutation, useCancelExecutionMutation } from '@/redux/services/extensions/extensionsApi';
5+
import { Extension, ExtensionListParams, SortDirection, ExtensionSortField, ExtensionCategory } from '@/redux/types/extension';
6+
import { useGetExtensionsQuery, useRunExtensionMutation, useCancelExecutionMutation, useGetExtensionCategoriesQuery } from '@/redux/services/extensions/extensionsApi';
77

88
export function useExtensions() {
99
const router = useRouter();
@@ -16,9 +16,11 @@ export function useExtensions() {
1616
const [itemsPerPage] = useState(9);
1717
const [runModalOpen, setRunModalOpen] = useState(false);
1818
const [selectedExtension, setSelectedExtension] = useState<Extension | null>(null);
19+
const [selectedCategory, setSelectedCategory] = useState<ExtensionCategory | null>(null);
1920

2021
const queryParams: ExtensionListParams = {
2122
search: searchTerm || undefined,
23+
category: selectedCategory || undefined,
2224
sort_by: sortConfig.key,
2325
sort_dir: sortConfig.direction,
2426
page: currentPage,
@@ -31,6 +33,8 @@ export function useExtensions() {
3133
error: apiError
3234
} = useGetExtensionsQuery(queryParams);
3335

36+
const { data: categories = [] } = useGetExtensionCategoriesQuery();
37+
3438
const extensions = response?.extensions || [];
3539
const totalPages = response?.total_pages || 0;
3640
const totalExtensions = response?.total || 0;
@@ -45,6 +49,11 @@ export function useExtensions() {
4549
setCurrentPage(1); // Reset to first page when sorting
4650
};
4751

52+
const handleCategoryChange = (value: string | null) => {
53+
setSelectedCategory((value as ExtensionCategory) || null);
54+
setCurrentPage(1);
55+
};
56+
4857
const handlePageChange = (page: number) => {
4958
setCurrentPage(page);
5059
};
@@ -81,13 +90,16 @@ export function useExtensions() {
8190
extensions,
8291
isLoading,
8392
error,
93+
categories,
8494
searchTerm,
8595
sortConfig,
8696
currentPage,
8797
totalPages,
8898
totalExtensions,
8999
handleSearchChange,
90100
handleSortChange,
101+
selectedCategory,
102+
handleCategoryChange,
91103
handlePageChange,
92104
handleInstall,
93105
handleViewDetails,

view/app/extensions/page.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import PageLayout from '@/components/layout/page-layout';
66
import ExtensionsHeader from '@/app/extensions/components/extensions-header';
77
import ExtensionsGrid from '@/app/extensions/components/extensions-grid';
88
import ExtensionsHero from '@/app/extensions/components/extensions-hero';
9+
import CategoryBadges from '@/app/extensions/components/category-badges';
910
import { useExtensions } from './hooks/use-extensions';
1011
import PaginationWrapper from '@/components/ui/pagination';
1112
import ExtensionInput from '@/app/extensions/components/extension-input';
@@ -16,13 +17,16 @@ export default function ExtensionsPage() {
1617
extensions,
1718
isLoading,
1819
error,
20+
categories,
1921
searchTerm,
2022
sortConfig,
2123
currentPage,
2224
totalPages,
2325
totalExtensions,
2426
handleSearchChange,
2527
handleSortChange,
28+
selectedCategory,
29+
handleCategoryChange,
2630
handlePageChange,
2731
handleInstall,
2832
handleViewDetails,
@@ -45,11 +49,20 @@ export default function ExtensionsPage() {
4549
/>
4650

4751
<div className="space-y-6">
48-
{totalExtensions > 0 && (
49-
<div className="text-sm text-muted-foreground self-end justify-end flex">
50-
Showing {extensions.length} of {totalExtensions} extensions
52+
<div className="flex items-start justify-between gap-4">
53+
<div className="flex-1 min-w-0">
54+
<CategoryBadges
55+
categories={categories}
56+
selected={selectedCategory}
57+
onChange={handleCategoryChange}
58+
/>
5159
</div>
52-
)}
60+
{totalExtensions > 0 && (
61+
<div className="text-sm text-muted-foreground whitespace-nowrap">
62+
Showing {extensions.length} of {totalExtensions} extensions
63+
</div>
64+
)}
65+
</div>
5366

5467
<ExtensionsGrid
5568
extensions={extensions}

view/redux/api-conf.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export enum IMAGEURLS {
129129

130130
export enum EXTENSIONURLS {
131131
GET_EXTENSIONS = 'v1/extensions',
132+
GET_CATEGORIES = 'v1/extensions/categories',
132133
GET_EXTENSION = 'v1/extensions/{id}',
133134
GET_EXTENSION_BY_ID = 'v1/extensions/by-extension-id/{extension_id}',
134135
FORK_EXTENSION = 'v1/extensions/{extension_id}/fork',

0 commit comments

Comments
 (0)