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 />;
};
Previous
Patterns