Frontend - Development
Frontend State Management
Redux Store Structure
Auth Slice
interface AuthState {
token: string | null;
isAuth: boolean;
setupStep: number;
}
// Actions
setToken(token: string)
nextSetupStep()
resetSetupStep()
logout()
Config Slice
interface ConfigState {
backendUrl: string;
githubConfig: {
clientId: string;
clientSecret: string;
} | null;
}
// Actions
setBackendUrl(url: string)
setGithubConfig(config: GithubConfiguration)
API Integration
Project Service
// Project endpoints
const projectApi = {
getAll: () => "GET /project",
getById: (id: string) => `GET /project/${id}`,
create: "POST /project",
update: (id: string) => `PUT /project/${id}`,
delete: (id: string) => `DELETE /project/${id}`,
}
// Query usage
const { data: projects } = useGetProjectsQuery();
const [createProject] = useCreateProjectMutation();
Authorization Service
// Authorization endpoints
const authorizationApi = {
create: "POST /authorization",
getAll: "GET /authorization",
delete: (id: string) => `DELETE /authorization/${id}`,
}
// Usage with error handling
const [createAuthorization] = useCreateAuthorizationMutation();
try {
await createAuthorization({
type: "oauth",
name: "github",
data: { code }
});
} catch (error) {
handleError(error);
}
WebSocket Integration
Connection Management
const useWebsockets = () => {
const socket = useRef<Socket>();
const { token } = useAppSelector((state) => state.auth);
useEffect(() => {
if (token) {
socket.current = io(SOCKET_URL, {
auth: { token },
transports: ["websocket"],
});
}
return () => socket.current?.disconnect();
}, [token]);
};
Project Updates
// Event types
interface ProjectEvent {
projectId: string;
type: "UPDATE" | "DELETE" | "CREATE";
data: any;
}
// Event handling
socket.on("project.update", (event: ProjectEvent) => {
handleProjectUpdate(event);
});
Custom Hooks
useStore
const useStore = () => {
const dispatch = useAppDispatch();
const state = useAppSelector((state) => state);
return { dispatch, state };
};
useConfiguration
const useConfiguration = () => {
const { githubConfig } = useAppSelector((state) => state.config);
const { data: configuration } = useGetConfigurationQuery();
return {
githubConfig: githubConfig || configuration?.github,
};
};
useDomain
const useDomain = (id?: string) => {
const [refetch, setRefetch] = useState(false);
const { data: domain } = useGetDomainQuery(id, { skip: !id });
const onDelete = id ? () => deleteDomain(id) : undefined;
const onRefetch = () => setRefetch(true);
return { domain, onDelete, onRefetch };
};
Error Handling
API Error Structure
interface ApiError {
status: number;
data: {
message: string;
error?: string;
};
}
Error Handling Pattern
try {
await mutation(data);
toast({
title: "Success",
variant: "default",
});
} catch (error: ApiError) {
toast({
title: "Error occurred",
description: error.data.message,
variant: "destructive",
});
}
Cache Management
RTK Query Cache Configuration
// Cache lifetime configuration
keepUnusedDataFor: 60, // seconds
// Tags for cache invalidation
tagTypes: ['Project', 'Domain', 'Service'],
Manual Cache Updates
// Optimistic updates
updateQueryData('getProjects', undefined, (draft) => {
draft.push(newProject);
});
// Cache invalidation
invalidateTags(['Project']);
Authentication Flow
Token Management
// Token storage
setToken: (state, action: PayloadAction<string>) => {
state.token = action.payload;
state.isAuth = true;
localStorage.setItem('token', action.payload);
};
// Token retrieval
const token = localStorage.getItem('token');
if (token) {
dispatch(setToken(token));
}
Protected Routes
const AuthenticatedGuard = () => {
const { isAuth } = useAppSelector((state) => state.auth);
const location = useLocation();
if (!isAuth) {
return <Navigate to="/" state={{ from: location }} replace />;
}
return <Outlet />;
};