feat: Add Step Library API foundation (Phase 1: B.1-B.3, B.7)

Implements foundational types and API clients for Step Library:

Task B.3 - TypeScript Types:
- Created types/step.ts with comprehensive interfaces
- Step, StepListItem, StepCategory types
- StepContent with instructions, help_text, commands
- StepListParams for filtering/sorting
- Rating and Review types
- StepCreate/StepUpdate DTOs

Task B.1 - Steps API Client:
- Created api/steps.ts following existing patterns
- CRUD operations (list, get, create, update, delete)
- Search endpoint with query
- Popular tags endpoint
- Rating/review operations (rate, updateRating, deleteRating, getReviews)

Task B.2 - Step Categories API Client:
- Created api/stepCategories.ts
- List and get operations for categories

Task B.7 - Update API Index:
- Exported stepsApi and stepCategoriesApi
- Available for import from '@/api'

Phase 1 foundation complete. Ready for Phase 2 (UI components).
Build tested successfully.

Related: Issue #10

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-03 19:05:05 -05:00
parent f93c8d84df
commit d52bfe2e27
4 changed files with 214 additions and 0 deletions

View File

@@ -6,3 +6,5 @@ export { default as inviteApi } from './invite'
export { default as tagsApi } from './tags'
export { default as categoriesApi } from './categories'
export { default as foldersApi } from './folders'
export { default as stepsApi } from './steps'
export { default as stepCategoriesApi } from './stepCategories'

View File

@@ -0,0 +1,16 @@
import apiClient from './client'
import type { StepCategory } from '@/types/step'
export const stepCategoriesApi = {
async list(): Promise<StepCategory[]> {
const response = await apiClient.get<StepCategory[]>('/step-categories')
return response.data
},
async get(id: string): Promise<StepCategory> {
const response = await apiClient.get<StepCategory>(`/step-categories/${id}`)
return response.data
}
}
export default stepCategoriesApi

72
frontend/src/api/steps.ts Normal file
View File

@@ -0,0 +1,72 @@
import apiClient from './client'
import type {
Step,
StepListItem,
StepCreate,
StepUpdate,
StepListParams,
PopularTag,
RatingCreate,
RatingUpdate,
Rating,
Review
} from '@/types/step'
export const stepsApi = {
async list(params?: StepListParams): Promise<StepListItem[]> {
const response = await apiClient.get<StepListItem[]>('/steps', { params })
return response.data
},
async get(id: string): Promise<Step> {
const response = await apiClient.get<Step>(`/steps/${id}`)
return response.data
},
async create(data: StepCreate): Promise<Step> {
const response = await apiClient.post<Step>('/steps', data)
return response.data
},
async update(id: string, data: StepUpdate): Promise<Step> {
const response = await apiClient.put<Step>(`/steps/${id}`, data)
return response.data
},
async delete(id: string): Promise<void> {
await apiClient.delete(`/steps/${id}`)
},
async search(query: string): Promise<StepListItem[]> {
const response = await apiClient.get<StepListItem[]>('/steps/search', {
params: { q: query }
})
return response.data
},
async getPopularTags(): Promise<PopularTag[]> {
const response = await apiClient.get<PopularTag[]>('/steps/popular-tags')
return response.data
},
async rate(id: string, data: RatingCreate): Promise<Rating> {
const response = await apiClient.post<Rating>(`/steps/${id}/ratings`, data)
return response.data
},
async updateRating(id: string, data: RatingUpdate): Promise<Rating> {
const response = await apiClient.put<Rating>(`/steps/${id}/ratings`, data)
return response.data
},
async deleteRating(id: string): Promise<void> {
await apiClient.delete(`/steps/${id}/ratings`)
},
async getReviews(id: string): Promise<Review[]> {
const response = await apiClient.get<Review[]>(`/steps/${id}/reviews`)
return response.data
}
}
export default stepsApi

124
frontend/src/types/step.ts Normal file
View File

@@ -0,0 +1,124 @@
// Step Library Types
export interface StepCommand {
label: string
command: string
command_type?: string
}
export interface StepContent {
instructions: string
help_text?: string
commands?: StepCommand[]
}
export interface Step {
id: string
title: string
step_type: 'decision' | 'action' | 'solution'
content: StepContent
visibility: 'private' | 'team' | 'public'
category_id?: string
category_name?: string
tags: string[]
usage_count: number
rating_average: number
rating_count: number
helpful_yes: number
helpful_no: number
is_featured: boolean
is_verified: boolean
created_by: string
author_name?: string
created_at: string
updated_at: string
}
export interface StepListItem {
id: string
title: string
step_type: string
visibility: string
category_id?: string
category_name?: string
tags: string[]
usage_count: number
rating_average: number
rating_count: number
is_featured: boolean
created_by: string
author_name?: string
created_at: string
}
export interface StepCategory {
id: string
name: string
description?: string
display_order: number
team_id?: string
is_active: boolean
}
export interface StepListParams {
visibility?: 'private' | 'team' | 'public'
category_id?: string
tags?: string[]
min_rating?: number
step_type?: 'decision' | 'action' | 'solution'
sort_by?: 'recent' | 'popular' | 'highest_rated' | 'most_used'
limit?: number
offset?: number
}
export interface PopularTag {
tag: string
count: number
}
export interface StepCreate {
title: string
step_type: 'decision' | 'action' | 'solution'
content: StepContent
visibility: 'private' | 'team' | 'public'
category_id?: string
tags?: string[]
}
export interface StepUpdate {
title?: string
step_type?: 'decision' | 'action' | 'solution'
content?: StepContent
visibility?: 'private' | 'team' | 'public'
category_id?: string
tags?: string[]
}
export interface RatingCreate {
rating: number
review_text?: string
verified_use: boolean
}
export interface RatingUpdate {
rating?: number
review_text?: string
}
export interface Rating {
id: string
step_id: string
user_id: string
rating: number
review_text?: string
verified_use: boolean
helpful_yes: number
helpful_no: number
created_at: string
updated_at: string
user_name?: string
}
export interface Review extends Rating {
// Same as Rating, just an alias for clarity
}