Skip to main content
phase-6-ui-integration Implement frontend UI and integrate with backend APIs — state management and API clients.
Triggers: UI integration, frontend-backend, API client
default: bkit:pipeline-guide
frontend: bkit:frontend-architect
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/popup-studio-ai/bkit-claude-code --skill phase-6-ui-integration명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... name phase-6-ui-integration classification capability classification-reason Pattern guidance may overlap with model's built-in knowledge as it improves deprecation-risk medium effort medium description Implement frontend UI and integrate with backend APIs — state management and API clients.
Triggers: UI integration, frontend-backend, API client
default: bkit:pipeline-guide
frontend: bkit:frontend-architect
allowed-tools ["Read","Write","Edit","Glob","Grep","Bash"] user-invocable false next-skill phase-7-seo-security pdca-phase do task-template [Phase-6] {feature}
Phase 6: UI Implementation + API Integration
Actual UI implementation and API integration
Purpose
Implement actual screens using design system components and integrate with APIs.
What to Do in This Phase
Page Implementation : Develop each screen
State Management : Handle client state
API Integration : Call backend APIs
Error Handling : Handle loading and error states
Deliverables
src/
├── pages/ # Page components
│ ├── index.tsx
│ ├── login.tsx
│ └── ...
├── features/ # Feature-specific components
│ ├── auth/
│ ├── product/
│ └── ...
└── hooks/ # API call hooks
├── useAuth.ts
└── useProducts.ts
docs/03-analysis/
└── ui-qa.md # QA results
PDCA Application
Plan : Define screens/features to implement
Design : Component structure, state management design
Do : UI implementation + API integration
Check : Zero Script QA
Act : Fix bugs and proceed to Phase 7
Level-wise Application
Level Application Method Starter Static UI only (no API integration) Dynamic Full integration Enterprise Full integration + optimization
API Client Architecture
Why is a Centralized API Client Needed?
Problem (Scattered API Calls) Solution (Centralized Client) Duplicate error handling logic Common error handler Distributed auth token handling Automatic token injection Inconsistent response formats Standardized response types Multiple changes when endpoint changes Single point of management Difficult testing/mocking Easy mock replacement
3-Layer API Client Structure
┌─────────────────────────────────────────────────────────┐
│ UI Components │
│ (pages, features, hooks) │
├─────────────────────────────────────────────────────────┤
│ Service Layer │
│ (Domain-specific API call functions) │
│ authService, productService, orderService, ... │
├─────────────────────────────────────────────────────────┤
│ API Client Layer │
│ (Common settings, interceptors, error handling) │
│ apiClient (axios/fetch wrapper) │
└─────────────────────────────────────────────────────────┘
Folder Structure src/
├── lib/
│ └── api/
│ ├── client.ts # API client (axios/fetch wrapper)
│ ├── interceptors.ts # Request/response interceptors
│ └── error-handler.ts # Error handling logic
├── services/
│ ├── auth.service.ts # Auth-related APIs
│ ├── product.service.ts # Product-related APIs
│ └── order.service.ts # Order-related APIs
├── types/
│ ├── api.types.ts # Common API types
│ ├── auth.types.ts # Auth domain types
│ └── product.types.ts # Product domain types
└── hooks/
├── useAuth.ts # Hooks using Service
└── useProducts.ts
API Client Implementation
1. Basic API Client (lib/api/client.ts)
import { ApiError , ApiResponse } from '@/types/api.types' ;
const BASE_URL = process.env .NEXT_PUBLIC_API_URL || '/api' ;
interface RequestConfig extends RequestInit {
params ?: Record <string , string >;
}
class ApiClient {
private baseUrl : string ;
constructor (baseUrl : string ) {
this .baseUrl = baseUrl;
}
private async request<T>(
endpoint : string ,
config : RequestConfig = {}
): Promise <ApiResponse <T>> {
const { params, ...init } = config;
const url = new URL (`${this .baseUrl} ${endpoint} ` );
if (params) {
Object .entries (params).forEach (([key, value] ) => {
url.searchParams .append (key, value);
});
}
const headers = new Headers (init.headers );
if (!headers.has ('Content-Type' )) {
headers.set ('Content-Type' , 'application/json' );
}
const token = this .getAuthToken ();
if (token) {
headers.set ('Authorization' , `Bearer ${token} ` );
}
try {
const response = await fetch (url.toString (), {
...init,
headers,
});
return this .handleResponse <T>(response);
} catch (error) {
throw this .handleNetworkError (error);
}
}
private async handleResponse<T>(response : Response ): Promise <ApiResponse <T>> {
const data = await response.json ();
if (!response.ok ) {
throw new ApiError (
data.error ?.code || 'UNKNOWN_ERROR' ,
data.error ?.message || 'An error occurred' ,
response.status ,
data.error ?.details
);
}
return data as ApiResponse <T>;
}
private handleNetworkError (error : unknown ): ApiError {
if (error instanceof TypeError && error.message === 'Failed to fetch' ) {
return new ApiError ('NETWORK_ERROR' , 'Please check your network connection.' , 0 );
}
return new ApiError ('UNKNOWN_ERROR' , 'An unknown error occurred.' , 0 );
}
private getAuthToken (): string | null {
if (typeof window === 'undefined' ) return null ;
return localStorage .getItem ('auth_token' );
}
get<T>(endpoint : string , params ?: Record <string , string >) {
return this .request <T>(endpoint, { method : 'GET' , params });
}
post<T>(endpoint : string , body ?: unknown ) {
return this .request <T>(endpoint, {
method : 'POST' ,
body : JSON .stringify (body),
});
}
put<T>(endpoint : string , body ?: unknown ) {
return this .request <T>(endpoint, {
method : 'PUT' ,
body : JSON .stringify (body),
});
}
patch<T>(endpoint : string , body ?: unknown ) {
return this .request <T>(endpoint, {
method : 'PATCH' ,
body : JSON .stringify (body),
});
}
delete <T>(endpoint : string ) {
return this .request <T>(endpoint, { method : 'DELETE' });
}
}
export const apiClient = new ApiClient (BASE_URL );
2. Common Type Definitions (types/api.types.ts)
export interface ApiResponse <T> {
data : T;
meta ?: {
timestamp : string ;
requestId ?: string ;
};
}
export interface PaginatedResponse <T> extends ApiResponse <T[]> {
pagination : {
page : number ;
limit : number ;
total : number ;
totalPages : number ;
};
}
export interface ApiErrorResponse {
error : {
code : string ;
message : string ;
details ?: Array <{
field : string ;
message : string ;
}>;
};
}
export class ApiError extends Error {
constructor (
public code : string ,
message : string ,
public status : number ,
public details ?: Array <{ field: string ; message: string }>
) {
super (message);
this .name = 'ApiError' ;
}
isValidationError (): boolean {
return this .code === 'VALIDATION_ERROR' && !!this .details ;
}
isAuthError (): boolean {
return this .status === 401 || this .code === 'UNAUTHORIZED' ;
}
isForbiddenError (): boolean {
return this .status === 403 || this .code === 'FORBIDDEN' ;
}
isNotFoundError (): boolean {
return this .status === 404 || this .code === 'NOT_FOUND' ;
}
}
export const ERROR_CODES = {
VALIDATION_ERROR : 'VALIDATION_ERROR' ,
UNAUTHORIZED : 'UNAUTHORIZED' ,
FORBIDDEN : 'FORBIDDEN' ,
NOT_FOUND : 'NOT_FOUND' ,
CONFLICT : 'CONFLICT' ,
INTERNAL_ERROR : 'INTERNAL_ERROR' ,
SERVICE_UNAVAILABLE : 'SERVICE_UNAVAILABLE' ,
NETWORK_ERROR : 'NETWORK_ERROR' ,
TIMEOUT_ERROR : 'TIMEOUT_ERROR' ,
} as const ;
export type ErrorCode = typeof ERROR_CODES [keyof typeof ERROR_CODES ];
Service Layer Pattern
Domain-specific Service Separation
import { apiClient } from '@/lib/api/client' ;
import { User , LoginRequest , LoginResponse , SignupRequest } from '@/types/auth.types' ;
export const authService = {
login (credentials : LoginRequest ) {
return apiClient.post <LoginResponse >('/auth/login' , credentials);
},
signup (data : SignupRequest ) {
return apiClient.post <User >('/auth/signup' , data);
},
logout ( ) {
return apiClient.post <void >('/auth/logout' );
},
getMe ( ) {
return apiClient.get <User >('/auth/me' );
},
refreshToken ( ) {
return apiClient.post <LoginResponse >('/auth/refresh' );
},
};
import { apiClient } from '@/lib/api/client' ;
import { Product , ProductFilter , CreateProductRequest } from '@/types/product.types' ;
import { PaginatedResponse } from '@/types/api.types' ;
export const productService = {
getList (filter ?: ProductFilter ) {
const params = filter ? {
page : String (filter.page || 1 ),
limit : String (filter.limit || 20 ),
...(filter.category && { category : filter.category }),
...(filter.search && { search : filter.search }),
} : undefined ;
return apiClient.get <PaginatedResponse <Product >>('/products' , params);
},
getById (id : string ) {
return apiClient.get <Product >(`/products/${id} ` );
},
create (data : CreateProductRequest ) {
return apiClient.post <Product >('/products' , data);
},
update (id : string , data : Partial <CreateProductRequest > ) {
return apiClient.patch <Product >(`/products/${id} ` , data);
},
delete (id : string ) {
return apiClient.delete <void >(`/products/${id} ` );
},
};
Error Handling Pattern
Global Error Handler
import { ApiError , ERROR_CODES } from '@/types/api.types' ;
import { toast } from 'sonner' ;
interface ErrorHandlerOptions {
showToast ?: boolean ;
redirectOnAuth ?: boolean ;
customMessages ?: Record <string , string >;
}
export function handleApiError (
error : unknown ,
options : ErrorHandlerOptions = {}
): void {
const { showToast = true , redirectOnAuth = true , customMessages = {} } = options;
if (!(error instanceof ApiError )) {
console .error ('Unexpected error:' , error);
if (showToast) {
toast.error ('An unknown error occurred.' );
}
return ;
}
const message = customMessages[error.code ] || error.message ;
switch (error.code ) {
case ERROR_CODES .UNAUTHORIZED :
if (redirectOnAuth && typeof window !== 'undefined' ) {
localStorage .removeItem ('auth_token' );
window .location .href = '/login' ;
}
break ;
case ERROR_CODES .FORBIDDEN :
if (showToast) toast.error ('You do not have permission.' );
break ;
case ERROR_CODES .NOT_FOUND :
if (showToast) toast.error ('The requested resource was not found.' );
break ;
case ERROR_CODES .VALIDATION_ERROR :
break ;
case ERROR_CODES .NETWORK_ERROR :
if (showToast) toast.error ('Please check your network connection.' );
break ;
default :
if (showToast) toast.error (message);
}
if (process.env .NODE_ENV === 'development' ) {
console .error (`[API Error] ${error.code} :` , {
message : error.message ,
status : error.status ,
details : error.details ,
});
}
}
Error Handling in Hooks
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' ;
import { productService } from '@/services/product.service' ;
import { handleApiError } from '@/lib/api/error-handler' ;
import { ProductFilter } from '@/types/product.types' ;
export function useProducts (filter ?: ProductFilter ) {
return useQuery ({
queryKey : ['products' , filter],
queryFn : () => productService.getList (filter),
throwOnError : false ,
meta : {
errorHandler : (error : unknown ) => handleApiError (error),
},
});
}
export function useCreateProduct ( ) {
const queryClient = useQueryClient ();
return useMutation ({
mutationFn : productService.create ,
onSuccess : () => {
queryClient.invalidateQueries ({ queryKey : ['products' ] });
},
onError : (error ) => {
handleApiError (error, {
customMessages : {
CONFLICT : 'Product name already exists.' ,
},
});
},
});
}
Client-Server Type Sharing
Methods for Type Consistency Method 1: Shared Package (Monorepo)
├── packages/
│ └── shared-types/ # Common types
│ ├── api.types.ts
│ ├── auth.types.ts
│ └── product.types.ts
├── apps/
│ ├── web/ # Frontend
│ └── api/ # Backend
Method 2: Auto-generate Types from API Spec
├── openapi.yaml # OpenAPI spec
└── scripts/
└── generate-types.ts # Type auto-generation script
Method 3: tRPC / GraphQL CodeGen
└── Auto-infer types from schema
Type Definition Example
export interface User {
id : string ;
email : string ;
name : string ;
role : 'user' | 'admin' ;
createdAt : string ;
updatedAt : string ;
}
export interface LoginRequest {
email : string ;
password : string ;
}
export interface LoginResponse {
user : User ;
token : string ;
expiresAt : string ;
}
export interface SignupRequest {
email : string ;
password : string ;
name : string ;
termsAgreed : boolean ;
}
API Integration Patterns
Basic Pattern (fetch) async function getProducts ( ) {
const response = await fetch ('/api/products' );
if (!response.ok ) throw new Error ('Failed to fetch' );
return response.json ();
}
React Query Pattern function useProducts ( ) {
return useQuery ({
queryKey : ['products' ],
queryFn : getProducts,
});
}
SWR Pattern function useProducts ( ) {
return useSWR ('/api/products' , fetcher);
}
State Management Guide Server state (API data) → React Query / SWR
Client state (UI state) → useState / useReducer
Global state (auth, etc.) → Context / Zustand
Form state → React Hook Form
Zero Script QA Application Validate UI behavior with logs:
[UI] Login button clicked
[STATE] isLoading: true
[API] POST /api/auth/login
[RESPONSE] { token: "...", user: {...} }
[STATE] isLoading: false, isLoggedIn: true
[NAVIGATE] → /dashboard
[RESULT] ✅ Login successful
API Integration Checklist
Architecture
Error Handling
Coding Conventions
Template See templates/pipeline/phase-6-ui.template.md
Next Phase Phase 7: SEO/Security → Features are complete, now optimize and strengthen security