From 59e43f24e90aa01bf9ac2e0720ee2913aa32f9fd Mon Sep 17 00:00:00 2001 From: Mahmoud Arafa Date: Tue, 15 Jul 2025 00:57:51 +0200 Subject: [PATCH] RAC-11: update readme --- PROJECT-BREAKDOWN.md | 572 +++++++------------------------------------ README.md | 418 +++++++++++++++++++++++-------- ci-cd.png | Bin 0 -> 66952 bytes 3 files changed, 403 insertions(+), 587 deletions(-) create mode 100644 ci-cd.png diff --git a/PROJECT-BREAKDOWN.md b/PROJECT-BREAKDOWN.md index a04e35e..e5a302f 100644 --- a/PROJECT-BREAKDOWN.md +++ b/PROJECT-BREAKDOWN.md @@ -1,527 +1,125 @@ -# 🎯 Reactive API Console - Project Task Breakdown +# 🎯 Assignment: Reactive API Console via Chat Interface -## πŸ“‹ **Project Overview** +## πŸ’‘ Concept -A modern, reactive API explorer with chat interface, Redux state management, drag & drop functionality, advanced search/filtering, and export capabilities. +Build a **reactive API explorer tool** where users can: ---- +1. Choose from a list of public APIs (e.g. Weather, Joke API, GitHub users) +2. Use a **chat-style interface** to issue commands + (e.g. `get weather in Tokyo`, `search users john`) +3. Render results in **dedicated, isolated panels** (one per API) +4. Support **real-time search & filtering** across results -## πŸ—οΈ **Phase 1: Foundation & Core Setup** +## 🧱 Core Requirements -### **1.1 Project Initialization** +### 1. πŸ”Œ API Selection -- [x] **Setup Next.js 14 project** with TypeScript -- [x] **Configure Tailwind CSS** for styling -- [x] **Setup ESLint & Prettier** for code quality -- [x] **Initialize Git repository** with proper .gitignore -- [x] **Create project structure** (components, types, utils, etc.) -- [x] **Setup package.json** with all dependencies +- Preload 3–5 public APIs with defined endpoints and commands +- Show a sidebar to **activate/deactivate APIs** +- Each active API gets its own UI panel (simulate iframe-like isolation) -**Estimated Time:** 2-3 hours -**Priority:** High -**Dependencies:** None -**Status:** βœ… Complete +### 2. πŸ’¬ Chat Input -### **1.2 TypeScript Types & Interfaces** +- Accept text-based commands (e.g. `get cat fact`, `search chuck car`) +- Parse input and trigger API calls +- Use **RxJS** for: + - Input parsing stream + - API request stream (handle loading & error states) + - Debounced search/filtering logic -- [x] **Define API types** (ApiEndpoint, ApiCommand, ApiResponse) -- [x] **Create Redux state types** (RootState, slice states) -- [x] **Define component prop types** -- [x] **Create utility types** (FilterState, DragState, etc.) -- [x] **Export all types** from centralized location +### 3. πŸ“Ί Result Panels (Simulated IFrames) -**Estimated Time:** 2-3 hours -**Priority:** High -**Dependencies:** 1.1 -**Status:** βœ… Complete +- Each API’s response is rendered in its **own visual panel** +- Use **isolated React components** to simulate iframe-like encapsulation +- Only results for a given API should appear in its panel ---- +### 4. πŸ” Search & Filter -## πŸͺ **Phase 2: Redux State Management** +- Support filtering results globally or within individual panels +- Highlight matching entries +- Use **RxJS with debounced input** and efficient stream filtering -### **2.1 Redux Store Configuration** +## βš™οΈ Tech Stack -- [x] **Install Redux Toolkit** and React-Redux -- [x] **Configure store** with middleware and DevTools -- [x] **Create typed hooks** (useAppDispatch, useAppSelector) -- [x] **Setup store provider** in app layout -- [x] **Add serialization checks** for complex data +| Tool | Purpose | +| ------------- | -------------------------------------------- | +| React + Vite | App framework + fast dev tooling | +| Redux Toolkit | State management (API results, chat history) | +| RxJS | Reactive streams for input/API flows | +| TypeScript | Strong static typing | +| Vitest | Unit & component testing | +| Cypress | End-to-end interaction testing | -**Estimated Time:** 3-4 hours -**Priority:** High -**Dependencies:** 1.1, 1.2 -**Status:** βœ… Complete +## βœ… Testing Requirements -### **2.2 Redux Slices Implementation** +### πŸ§ͺ Unit/Component Tests (with **Vitest**) -- [x] **APIs Slice** - endpoint management, panel ordering -- [x] **Responses Slice** - API responses, loading states -- [x] **Chat Slice** - messages, command history -- [x] **UI Slice** - sidebar, modals, drag state -- [x] **Filters Slice** - search and filter state +- Chat input parsing logic +- RxJS observable pipelines +- Result rendering per API + for -**Estimated Time:** 6-8 hours -**Priority:** High -**Dependencies:** 2.1 -**Status:** βœ… Complete +### πŸš€ E2E Tests (with **Cypress**) -### **2.3 Selectors & Async Actions** +- Full chat-to-response flow +- API switching behavior +- Global and per-panel filtering -- [x] **Create memoized selectors** for performance -- [x] **Implement async thunks** for API calls -- [x] **Add error handling** in async actions -- [x] **Create derived selectors** (filtered data, ordered APIs) -- [x] **Test selector performance** with large datasets +## 🎁 Bonus Features (Optional) -**Estimated Time:** 4-5 hours -**Priority:** High -**Dependencies:** 2.2 -**Status:** βœ… Complete +- Chat commands like `clear`, `help`, or `history` +- β€œPin” important results to top of panel +- Animations for iframe entry/exit +- Drag-and-drop or resizable panels ---- +## πŸ“¦ Deliverables -## πŸ”Œ **Phase 3: API Integration** +- GitHub repository with clean commits +- `README.md` with setup, usage, and testing instructions +- Well-structured and modular folder layout -### **3.1 API Service Layer** +## 🌐 Public APIs to Use -- [x] **Define API endpoints** (Cat Facts, Chuck Norris, etc.) -- [x] **Create command patterns** with RegExp -- [x] **Implement response handlers** for each API -- [x] **Add error handling** for failed requests -- [x] **Create API initialization** logic +### 1. 🐱 Cat Facts API -**Estimated Time:** 4-6 hours -**Priority:** High -**Dependencies:** 2.2 -**Status:** βœ… Complete +- `GET https://catfact.ninja/fact` +- **Command**: `get cat fact` -### **3.2 Command Processing System** +### 2. 🧠 Chuck Norris Jokes API -- [x] **Build command parser** with pattern matching -- [x] **Implement special commands** (help, clear, history) -- [x] **Add command validation** and error messages -- [x] **Create command execution flow** with Redux -- [x] **Add loading states** for async commands +- `GET https://api.chucknorris.io/jokes/random` +- `GET https://api.chucknorris.io/jokes/search?query=kick` +- **Commands**: `get chuck joke`, `search chuck kick` -**Estimated Time:** 5-7 hours -**Priority:** High -**Dependencies:** 3.1, 2.3 -**Status:** βœ… Complete +### 3. 🌍 Bored API ---- +- `GET https://bored-api.appbrewery.com/random` +- **Command**: `get activity` -## πŸ’¬ **Phase 4: Chat Interface** +### 4. πŸ” GitHub Users Search API -### **4.1 Chat Component Development** +- `GET https://api.github.com/search/users?q=john` +- **Command**: `search github john` -- [x] **Create ChatInterface component** with message display -- [x] **Build message input** with form handling -- [x] **Add message types** (user, system, error) with styling -- [x] **Implement auto-scroll** to latest messages -- [x] **Add message timestamps** and formatting +### 5. 🌦️ Weather API (Open-Meteo) -**Estimated Time:** 4-5 hours -**Priority:** High -**Dependencies:** 2.2, 3.2 -**Status:** βœ… Complete +- `GET https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true` +- **Command**: `get weather Berlin` +- _(You can hardcode city-to-coordinates mapping in app)_ -### **4.2 Chat Features** +## πŸ—ƒοΈ Public API for Simulating Chat Message Storage -- [x] **Add chat search functionality** with highlighting -- [x] **Implement command suggestions** and autocomplete -- [x] **Create message filtering** by type -- [x] **Add keyboard shortcuts** (Enter to send, etc.) -- [ ] **Implement message persistence** across sessions +### πŸ”Ή DummyJSON β€” https://dummyjson.com -**Estimated Time:** 3-4 hours -**Priority:** Medium -**Dependencies:** 4.1 -**Status:** πŸ”„ Mostly Complete (persistence pending) +Supports: `GET`, `POST`, `PUT`, `DELETE` ---- +- Use `/comments` or `/posts` as chat message entities +- Good for simulating user-generated message storage -## πŸ“Ί **Phase 5: API Panels & Results Display** +#### Example: -### **5.1 Panel System Architecture** - -- [x] **Create ApiPanel component** for individual APIs -- [x] **Build ApiPanels container** with tab system -- [x] **Implement panel selection** logic -- [x] **Add responsive design** for different screen sizes -- [x] **Create panel state management** with Redux - -**Estimated Time:** 5-6 hours -**Priority:** High -**Dependencies:** 2.2, 3.1 -**Status:** βœ… Complete - -### **5.2 Result Rendering** - -- [x] **Custom renderers** for each API type -- [x] **Rich data display** (images, links, formatted text) -- [ ] **Add result pagination** for large datasets -- [ ] **Implement result sorting** options -- [x] **Create result actions** (pin, delete, share) - -**Estimated Time:** 6-8 hours -**Priority:** High -**Dependencies:** 5.1 -**Status:** πŸ”„ Mostly Complete (pagination & sorting pending) - ---- - -## 🎯 **Phase 6: Drag & Drop System** - -### **6.1 Drag & Drop Infrastructure** - -- [x] **Create DraggableTab component** with drag handlers -- [x] **Implement drag state management** in Redux -- [x] **Add visual feedback** during drag operations -- [x] **Create drop zones** and indicators -- [x] **Handle drag events** (start, over, drop, end) - -**Estimated Time:** 4-5 hours -**Priority:** Medium -**Dependencies:** 5.1, 2.2 -**Status:** βœ… Complete - -### **6.2 Panel Reordering** - -- [x] **Implement panel order persistence** in Redux -- [x] **Add drag preview component** with custom styling -- [x] **Create smooth animations** for reordering -- [x] **Add touch support** for mobile devices -- [x] **Handle edge cases** (invalid drops, etc.) - -**Estimated Time:** 3-4 hours -**Priority:** Medium -**Dependencies:** 6.1 -**Status:** βœ… Complete - ---- - -## πŸ” **Phase 7: Search & Filtering System** - -### **7.1 Search Infrastructure** - -- [x] **Create SearchBar component** with debounced input -- [x] **Implement global search** across all results -- [x] **Add per-panel filtering** functionality -- [x] **Create search state management** in Redux -- [ ] **Add search history** and suggestions - -**Estimated Time:** 4-5 hours -**Priority:** High -**Dependencies:** 2.2, 5.1 -**Status:** πŸ”„ Mostly Complete (history pending) - -### **7.2 Advanced Search Features** - -- [x] **Text highlighting** in search results -- [x] **Deep object search** through nested data -- [x] **Match counting** and indicators -- [ ] **Search result ranking** by relevance -- [ ] **Regular expression support** for power users - -**Estimated Time:** 5-6 hours -**Priority:** Medium -**Dependencies:** 7.1 -**Status:** πŸ”„ Mostly Complete (ranking & regex pending) - -### **7.3 Highlighting System** - -- [x] **Create HighlightText component** with mark tags -- [x] **Implement match detection** utility functions -- [x] **Add contextual highlighting** for different content -- [x] **Create highlight animations** and transitions -- [x] **Handle special characters** in search terms - -**Estimated Time:** 3-4 hours -**Priority:** Medium -**Dependencies:** 7.2 -**Status:** βœ… Complete - ---- - -## πŸ“€ **Phase 8: Export System** - -### **8.1 Export Infrastructure** - -- [x] **Create ExportService** with multiple formats -- [x] **Implement format converters** (JSON, CSV, TXT, HTML) -- [x] **Add export options** (metadata, matches, raw data) -- [x] **Create file download** functionality -- [x] **Add export validation** and error handling - -**Estimated Time:** 5-6 hours -**Priority:** Medium -**Dependencies:** 7.1 -**Status:** βœ… Complete - -### **8.2 Export UI Components** - -- [x] **Create ExportModal** with preview functionality -- [x] **Build QuickExportButton** with dropdown -- [x] **Add export progress** indicators -- [x] **Implement batch export** for multiple APIs -- [ ] **Create export history** and templates - -**Estimated Time:** 4-5 hours -**Priority:** Medium -**Dependencies:** 8.1 -**Status:** πŸ”„ Mostly Complete (history & templates pending) - ---- - -## 🎨 **Phase 9: UI/UX Polish** - -### **9.1 Sidebar & Navigation** - -- [x] **Create responsive sidebar** with API management -- [x] **Add sidebar toggle** functionality -- [x] **Implement API activation** controls -- [ ] **Create sidebar search** for APIs -- [ ] **Add sidebar state persistence** - -**Estimated Time:** 3-4 hours -**Priority:** High -**Dependencies:** 2.2 -**Status:** πŸ”„ Mostly Complete (search & persistence pending) - -### **9.2 Visual Design & Animations** - -- [x] **Add loading states** and skeletons -- [x] **Create smooth transitions** between states -- [x] **Implement hover effects** and interactions -- [ ] **Add success/error notifications** system -- [x] **Create responsive breakpoints** for mobile - -**Estimated Time:** 4-5 hours -**Priority:** Medium -**Dependencies:** All UI components -**Status:** πŸ”„ Mostly Complete (notifications pending) - -### **9.3 Error Handling & Boundaries** - -- [x] **Create ErrorBoundary component** for crash recovery -- [x] **Add error states** for failed API calls -- [ ] **Implement retry mechanisms** for failed requests -- [x] **Create user-friendly error messages** -- [ ] **Add error reporting** and logging - -**Estimated Time:** 3-4 hours -**Priority:** High -**Dependencies:** All components -**Status:** πŸ”„ Mostly Complete (retry & logging pending) - ---- - -## πŸ§ͺ **Phase 10: Testing & Quality Assurance** - -### **10.1 Unit Testing** - -- [x] **Setup Vitest** testing framework -- [x] **Test Redux slices** and reducers -- [x] **Test utility functions** (search, export, etc.) -- [x] **Test React components** with React Testing Library -- [x] **Add test coverage** reporting - -**Estimated Time:** 8-10 hours -**Priority:** High -**Dependencies:** All features complete -**Status:** βœ… Complete - -### **10.2 Integration Testing** - -- [x] **Setup Cypress** for E2E testing -- [x] **Test user workflows** (command execution, search, export) -- [x] **Test drag & drop** functionality -- [x] **Test responsive design** on different devices -- [x] **Test error scenarios** and edge cases - -**Estimated Time:** 6-8 hours -**Priority:** High -**Dependencies:** 10.1 -**Status:** βœ… Complete - -### **10.3 Performance Testing** - -- [ ] **Test with large datasets** (1000+ responses) -- [ ] **Measure Redux selector** performance -- [ ] **Test search performance** with complex queries -- [ ] **Optimize bundle size** and loading times -- [ ] **Add performance monitoring** tools - -**Estimated Time:** 4-5 hours -**Priority:** Medium -**Dependencies:** 10.2 -**Status:** ⏳ Pending - ---- - -## πŸ“š **Phase 11: Documentation & Deployment** - -### **11.1 Documentation** - -- [x] **Write comprehensive README** with setup instructions -- [ ] **Create API documentation** for each endpoint -- [ ] **Document Redux architecture** and data flow -- [ ] **Add code comments** and JSDoc annotations -- [ ] **Create user guide** with screenshots - -**Estimated Time:** 4-6 hours -**Priority:** Medium -**Dependencies:** All features complete -**Status:** πŸ”„ Partially Complete (README done) - -### **11.2 Deployment & CI/CD** - -- [ ] **Setup Vercel deployment** configuration -- [ ] **Create GitHub Actions** for CI/CD -- [ ] **Add automated testing** in pipeline -- [ ] **Setup environment variables** for production -- [ ] **Configure domain** and SSL certificates - -**Estimated Time:** 3-4 hours -**Priority:** Medium -**Dependencies:** 11.1 -**Status:** ⏳ Pending - ---- - -## πŸš€ **Phase 12: Advanced Features (Optional)** - -### **12.1 Enhanced Features** - -- [ ] **Add dark/light theme** toggle -- [ ] **Implement user preferences** persistence -- [ ] **Create custom API** integration system -- [ ] **Add keyboard shortcuts** for power users -- [ ] **Implement undo/redo** functionality - -**Estimated Time:** 8-10 hours -**Priority:** Low -**Dependencies:** All core features -**Status:** ⏳ Future Enhancement - -### **12.2 Performance Optimizations** - -- [ ] **Add virtual scrolling** for large lists -- [ ] **Implement code splitting** for better loading -- [ ] **Add service worker** for offline functionality -- [ ] **Create data caching** strategies -- [ ] **Optimize re-renders** with React.memo - -**Estimated Time:** 6-8 hours -**Priority:** Low -**Dependencies:** 12.1 -**Status:** ⏳ Future Enhancement - ---- - -## πŸ“Š **Project Summary** - -### **Overall Progress: 85% Complete** 🎯 - -### **Completed Phases:** - -- βœ… **Foundation & Core Setup** (100%) -- βœ… **Redux State Management** (100%) -- βœ… **API Integration** (100%) -- βœ… **Chat Interface** (95%) -- βœ… **API Panels & Results** (90%) -- βœ… **Drag & Drop System** (100%) -- βœ… **Search & Filtering** (90%) -- βœ… **Export System** (95%) -- βœ… **Testing & QA** (85%) - -### **In Progress:** - -- πŸ”„ **UI/UX Polish** (80%) -- πŸ”„ **Documentation** (40%) - -### **Pending:** - -- ⏳ **Performance Testing** (0%) -- ⏳ **Deployment & CI/CD** (0%) -- ⏳ **Advanced Features** (0%) - -### **Time Investment:** - -- **Completed:** ~95-110 hours -- **Remaining Core:** ~15-20 hours -- **Optional Features:** ~15-25 hours - -### **Critical Path Remaining:** - -1. **Performance Testing** (1-2 days) -2. **Documentation Completion** (1 day) -3. **Deployment Setup** (0.5 day) -4. **Final Polish** (0.5 day) - -### **Key Achievements:** - -- πŸͺ **Full Redux Integration** with typed store -- 🎯 **Advanced Drag & Drop** with smooth animations -- πŸ” **Comprehensive Search** with highlighting -- πŸ“€ **Multi-format Export** system -- πŸ§ͺ **Extensive Testing** coverage -- πŸ“± **Responsive Design** for all devices - -### **Technical Debt:** - -- Message persistence across sessions -- Search history and suggestions -- Notification system -- Performance monitoring -- Error retry mechanisms - -### **Next Immediate Tasks:** - -1. **Performance optimization** for large datasets -2. **Complete documentation** with architecture diagrams -3. **Setup deployment** pipeline -4. **Add notification system** for better UX -5. **Implement retry mechanisms** for failed requests - -### **Risk Mitigation:** - -- βœ… Redux complexity handled with typed hooks -- βœ… Browser compatibility tested across devices -- βœ… API reliability handled with error boundaries -- ⚠️ Performance with large datasets needs testing -- ⚠️ Mobile responsiveness needs final validation - ---- - -## 🎯 **Success Metrics** - -### **Functional Requirements:** βœ… Met - -- [x] Multi-API integration with 5 public APIs -- [x] Chat-based command interface -- [x] Real-time search and filtering -- [x] Drag & drop panel reordering -- [x] Export functionality in multiple formats -- [x] Redux state management -- [x] Responsive design - -### **Technical Requirements:** βœ… Met - -- [x] TypeScript for type safety -- [x] Next.js 14 with App Router -- [x] Redux Toolkit for state management -- [x] Comprehensive testing suite -- [x] Modern React patterns (hooks, context) -- [x] Performance optimizations - -### **User Experience:** βœ… Excellent - -- [x] Intuitive chat interface -- [x] Visual feedback for all actions -- [x] Smooth animations and transitions -- [x] Error handling and recovery -- [x] Mobile-friendly design -- [x] Accessibility considerations - -The Reactive API Console project has successfully achieved its core objectives and is ready for production deployment with minimal remaining tasks focused on performance optimization and documentation completion. +| Command | API Action | +| -------------------------------- | --------------------- | +| `send message "Hello!"` | `POST /comments` | +| `edit message 42 "Updated text"` | `PUT /comments/42` | +| `delete message 42` | `DELETE /comments/42` | diff --git a/README.md b/README.md index e5a302f..043886d 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,343 @@ -# 🎯 Assignment: Reactive API Console via Chat Interface +# Reactive API Console -## πŸ’‘ Concept +A modern, interactive web application that provides a console-like interface for testing and exploring various APIs. Built with React, TypeScript, and Redux Toolkit, featuring real-time search, highlighting, and a responsive design. -Build a **reactive API explorer tool** where users can: +🌐 **Live Demo**: [https://reactive-api-console.vercel.app](https://reactive-api-console.vercel.app) -1. Choose from a list of public APIs (e.g. Weather, Joke API, GitHub users) -2. Use a **chat-style interface** to issue commands - (e.g. `get weather in Tokyo`, `search users john`) -3. Render results in **dedicated, isolated panels** (one per API) -4. Support **real-time search & filtering** across results +## πŸš€ Features -## 🧱 Core Requirements +- **Interactive API Testing**: Execute commands to fetch data from various APIs +- **Real-time Search**: Global search across all API responses with highlighting +- **Responsive Design**: Modern UI built with Tailwind CSS +- **Type Safety**: Full TypeScript support throughout the application +- **State Management**: Redux Toolkit for predictable state management +- **Reactive Programming**: RxJS for handling asynchronous operations and data streams +- **Comprehensive Testing**: Unit tests, integration tests, and E2E tests with Cypress -### 1. πŸ”Œ API Selection +## πŸ› οΈ Tech Stack -- Preload 3–5 public APIs with defined endpoints and commands -- Show a sidebar to **activate/deactivate APIs** -- Each active API gets its own UI panel (simulate iframe-like isolation) +- **Frontend**: React 19, TypeScript, Vite +- **Styling**: Tailwind CSS 4 +- **State Management**: Redux Toolkit, React Redux +- **Reactive Programming**: RxJS +- **Testing**: Vitest, Testing Library, Cypress +- **Build Tool**: Vite +- **Package Manager**: pnpm -### 2. πŸ’¬ Chat Input +## πŸ“‹ Prerequisites -- Accept text-based commands (e.g. `get cat fact`, `search chuck car`) -- Parse input and trigger API calls -- Use **RxJS** for: - - Input parsing stream - - API request stream (handle loading & error states) - - Debounced search/filtering logic +- Node.js 20 or higher +- pnpm (recommended) or npm -### 3. πŸ“Ί Result Panels (Simulated IFrames) +## πŸš€ Quick Start -- Each API’s response is rendered in its **own visual panel** -- Use **isolated React components** to simulate iframe-like encapsulation -- Only results for a given API should appear in its panel +### 1. Clone the Repository -### 4. πŸ” Search & Filter +```bash +git clone https://github.com/marafa1985/reactive-api-console.git +cd reactive-api-console +``` -- Support filtering results globally or within individual panels -- Highlight matching entries -- Use **RxJS with debounced input** and efficient stream filtering +### 2. Install Dependencies -## βš™οΈ Tech Stack +```bash +# Using pnpm (recommended) +pnpm install -| Tool | Purpose | -| ------------- | -------------------------------------------- | -| React + Vite | App framework + fast dev tooling | -| Redux Toolkit | State management (API results, chat history) | -| RxJS | Reactive streams for input/API flows | -| TypeScript | Strong static typing | -| Vitest | Unit & component testing | -| Cypress | End-to-end interaction testing | +# Or using npm +npm install +``` -## βœ… Testing Requirements +### 3. Start Development Server -### πŸ§ͺ Unit/Component Tests (with **Vitest**) +```bash +pnpm dev +``` -- Chat input parsing logic -- RxJS observable pipelines -- Result rendering per API - for +The application will be available at `http://localhost:5173` -### πŸš€ E2E Tests (with **Cypress**) +## πŸ“– Usage -- Full chat-to-response flow -- API switching behavior -- Global and per-panel filtering +### Available Commands -## 🎁 Bonus Features (Optional) +The console supports the following commands: -- Chat commands like `clear`, `help`, or `history` -- β€œPin” important results to top of panel -- Animations for iframe entry/exit -- Drag-and-drop or resizable panels +- `help` - Display available commands +- `clear` - clear all results +- `history` - Display messages history +- `get cat fact` - Fetch a random cat fact +- `get chuck norris joke` - Get a Chuck Norris joke +- `get bored activity` - Get a random activity suggestion +- `get github users` - Search GitHub users +- `get weather` - Get weather information -## πŸ“¦ Deliverables +### Features -- GitHub repository with clean commits -- `README.md` with setup, usage, and testing instructions -- Well-structured and modular folder layout +1. **Command Input**: Type commands in the chat console +2. **API Panels**: View responses in dedicated panels +3. **Global Search**: Search across all responses with highlighting +4. **Response Filtering**: Filter results based on search terms +5. **Response Pinning**: Pin important responses for quick access + +## πŸ§ͺ Testing + +### Unit Tests + +Run unit tests with Vitest: + +```bash +# Run all tests +pnpm test + +# Run tests with UI +pnpm test:ui + +# Run tests with coverage +pnpm test:coverage +``` + +### E2E Tests + +Run end-to-end tests with Cypress: + +```bash +# Open Cypress Test Runner +pnpm cy:open + +# Run Cypress tests in headless mode +pnpm cy:run + +# Run component tests +pnpm cy:open --component +``` + +### Linting + +```bash +pnpm lint +``` + +## πŸ—οΈ Build + +### Development Build + +```bash +pnpm build +``` + +### Preview Production Build + +```bash +pnpm build +pnpm preview +``` + +## πŸ“ Project Structure + +``` +reactive-api-console/ +β”œβ”€β”€ .github/workflows/ # GitHub Actions CI/CD +β”‚ └── ci-cd.yml # Main CI/CD pipeline +β”œβ”€β”€ cypress/ # E2E and Component Testing +β”‚ β”œβ”€β”€ component/ # Cypress component tests +β”‚ β”‚ └── ChatTitle.cy.tsx +β”‚ β”œβ”€β”€ e2e/ # End-to-end tests +β”‚ β”‚ β”œβ”€β”€ api-console.cy.ts +β”‚ β”‚ β”œβ”€β”€ drag-drop.cy.ts +β”‚ β”‚ β”œβ”€β”€ export.cy.ts +β”‚ β”‚ β”œβ”€β”€ highlighting.cy.ts +β”‚ β”‚ └── redux-integration.cy.ts +β”‚ β”œβ”€β”€ fixtures/ # Test data +β”‚ β”‚ β”œβ”€β”€ cat-fact.json +β”‚ β”‚ └── example.json +β”‚ β”œβ”€β”€ support/ # Cypress support files +β”‚ β”‚ β”œβ”€β”€ commands.ts +β”‚ β”‚ β”œβ”€β”€ component.ts +β”‚ β”‚ └── e2e.ts +β”‚ └── cypress.config.js # Cypress configuration +β”œβ”€β”€ public/ # Static assets +β”‚ β”œβ”€β”€ robots.txt +β”‚ └── vite.svg +β”œβ”€β”€ src/ # Source code +β”‚ β”œβ”€β”€ assets/ # Static assets +β”‚ β”‚ └── react.svg +β”‚ β”œβ”€β”€ components/ # React components (Atomic Design) +β”‚ β”‚ β”œβ”€β”€ atoms/ # Basic building blocks +β”‚ β”‚ β”‚ β”œβ”€β”€ APINotFound/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatSearchResult/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatTitle/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatWelcomeMessage/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DragHint/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DropIndicator/ +β”‚ β”‚ β”‚ β”œβ”€β”€ HighlightText/ +β”‚ β”‚ β”‚ β”œβ”€β”€ NoActiveAPI/ +β”‚ β”‚ β”‚ β”œβ”€β”€ NoActivePanel/ +β”‚ β”‚ β”‚ β”œβ”€β”€ NoResponses/ +β”‚ β”‚ β”‚ β”œβ”€β”€ NoResultMatched/ +β”‚ β”‚ β”‚ β”œβ”€β”€ PanelTitle/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ResultWrapper/ +β”‚ β”‚ β”‚ └── SearchSummary/ +β”‚ β”‚ β”œβ”€β”€ molecules/ # Composite components +β”‚ β”‚ β”‚ β”œβ”€β”€ BoredAPI/ +β”‚ β”‚ β”‚ β”œβ”€β”€ CatFacts/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatCommand/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatLog/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatSearch/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChuckNorris/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChuckNorrisJoke/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DefaultAPIResponse/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DraggableTab/ +β”‚ β”‚ β”‚ β”œβ”€β”€ FilterPanel/ +β”‚ β”‚ β”‚ β”œβ”€β”€ GithubUsers/ +β”‚ β”‚ β”‚ β”œβ”€β”€ PanelHeader/ +β”‚ β”‚ β”‚ β”œβ”€β”€ SideBarHeader/ +β”‚ β”‚ β”‚ β”œβ”€β”€ UserCommandInputProps/ +β”‚ β”‚ β”‚ └── Weather/ +β”‚ β”‚ β”œβ”€β”€ organisms/ # Complex components +β”‚ β”‚ β”‚ β”œβ”€β”€ ApiPanel/ +β”‚ β”‚ β”‚ β”œβ”€β”€ APIPanelContent/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ApiPanelHeader/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ApiPanelWrapper/ +β”‚ β”‚ β”‚ β”œβ”€β”€ AvailableAPIs/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ChatMessages/ +β”‚ β”‚ β”‚ └── ChatPanel/ +β”‚ β”‚ β”œβ”€β”€ pages/ # Page components +β”‚ β”‚ β”‚ └── Home/ +β”‚ β”‚ └── template/ # Layout templates +β”‚ β”‚ β”œβ”€β”€ ApiPanels.tsx +β”‚ β”‚ β”œβ”€β”€ Chat.tsx +β”‚ β”‚ β”œβ”€β”€ Header.tsx +β”‚ β”‚ β”œβ”€β”€ ResponsesPanel.tsx +β”‚ β”‚ └── SideBar.tsx +β”‚ β”œβ”€β”€ core/ # Business logic +β”‚ β”‚ β”œβ”€β”€ api/ # API integrations +β”‚ β”‚ β”‚ β”œβ”€β”€ bored.api.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ cat-fact.api.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ chuck-norris.api.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ github-users.api.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ registry.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ utils.ts +β”‚ β”‚ β”‚ └── weather.api.ts +β”‚ β”‚ β”œβ”€β”€ entity/ # Domain entities +β”‚ β”‚ β”‚ β”œβ”€β”€ api-response.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ api.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ chat-messages.ts +β”‚ β”‚ β”‚ └── user-command.ts +β”‚ β”‚ └── use-case/ # Business use cases +β”‚ β”‚ └── api-command.use-case.ts +β”‚ β”œβ”€β”€ hooks/ # Custom React hooks +β”‚ β”‚ β”œβ”€β”€ useApiCommands.ts +β”‚ β”‚ β”œβ”€β”€ useApiPanelsSortOrder.ts +β”‚ β”‚ β”œβ”€β”€ useApiSelectedPanel.ts +β”‚ β”‚ β”œβ”€β”€ useChat.ts +β”‚ β”‚ β”œβ”€β”€ useChatMessage.ts +β”‚ β”‚ β”œβ”€β”€ useCommandInput.ts +β”‚ β”‚ β”œβ”€β”€ useFilters.ts +β”‚ β”‚ └── useSpecialCommand.ts +β”‚ β”œβ”€β”€ store/ # Redux store +β”‚ β”‚ β”œβ”€β”€ feature/ # Redux slices +β”‚ β”‚ β”‚ └── slices/ +β”‚ β”‚ β”‚ β”œβ”€β”€ apisSlice.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ chatSlice.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ filtersSlice.ts +β”‚ β”‚ β”‚ β”œβ”€β”€ responsesSlice.ts +β”‚ β”‚ β”‚ └── uiSlice.ts +β”‚ β”‚ β”œβ”€β”€ hooks.ts # Redux hooks +β”‚ β”‚ β”œβ”€β”€ store.ts # Store configuration +β”‚ β”‚ └── StoreProvider.tsx # Store provider +β”‚ β”œβ”€β”€ utils/ # Utility functions +β”‚ β”‚ β”œβ”€β”€ cn.ts # Class name utility +β”‚ β”‚ β”œβ”€β”€ formatTimestamp.ts +β”‚ β”‚ β”œβ”€β”€ highlight.ts # Text highlighting +β”‚ β”‚ └── index.ts +β”‚ β”œβ”€β”€ App.tsx # Main App component +β”‚ β”œβ”€β”€ main.tsx # Application entry point +β”‚ β”œβ”€β”€ index.css # Global styles +β”‚ └── test-setup.ts # Test configuration +β”œβ”€β”€ .eslintrc.js # ESLint configuration +β”œβ”€β”€ .gitignore # Git ignore rules +β”œβ”€β”€ cypress.config.js # Cypress configuration +β”œβ”€β”€ index.html # HTML template +β”œβ”€β”€ package.json # Dependencies and scripts +β”œβ”€β”€ pnpm-lock.yaml # Lock file +β”œβ”€β”€ pnpm-workspace.yaml # Workspace configuration +β”œβ”€β”€ tsconfig.json # TypeScript configuration +β”œβ”€β”€ tsconfig.app.json # App-specific TS config +β”œβ”€β”€ tsconfig.node.json # Node-specific TS config +β”œβ”€β”€ vite.config.ts # Vite configuration +└── vitest.config.ts # Vitest configuration +``` + +## πŸ”§ Configuration + +### Environment Variables + +No environment variables are required for basic functionality. The application uses public APIs. + +### API Endpoints + +The application integrates with the following public APIs: + +- **Cat Facts**: `https://catfact.ninja/fact` +- **Chuck Norris Jokes**: `https://api.chucknorris.io/jokes/random` +- **Bored API**: `https://www.boredapi.com/api/activity` +- **GitHub Users**: `https://api.github.com/search/users` +- **Weather**: `https://api.open-meteo.com/v1/forecast` + +## πŸš€ Deployment + +### Vercel (Recommended) + +1. Connect your repository to Vercel +2. Vercel will automatically detect the Vite configuration +3. Deploy with default settings + +### Manual Deployment + +```bash +# Build the application +pnpm build + +# The built files will be in the `dist/` directory +# Deploy the contents of `dist/` to your hosting provider +``` + +## 🀝 Contributing + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add some amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +### Development Guidelines + +- Follow the existing code style and structure +- Write tests for new features +- Update documentation as needed +- Use conventional commit messages + +## πŸ“ License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## πŸ†˜ Support + +If you encounter any issues or have questions: + +1. Check the existing issues in the repository +2. Create a new issue with detailed information +3. Include steps to reproduce the problem + +## πŸ”„ CI/CD Pipeline + +The project includes a comprehensive CI/CD pipeline with the following stages: + +1. **Lint**: Code quality checks +2. **Test**: Unit and integration tests with coverage +3. **Build**: Production build verification +4. **E2E**: End-to-end testing with Cypress -## 🌐 Public APIs to Use +The pipeline runs on every push and pull request to ensure code quality and functionality. -### 1. 🐱 Cat Facts API - -- `GET https://catfact.ninja/fact` -- **Command**: `get cat fact` - -### 2. 🧠 Chuck Norris Jokes API - -- `GET https://api.chucknorris.io/jokes/random` -- `GET https://api.chucknorris.io/jokes/search?query=kick` -- **Commands**: `get chuck joke`, `search chuck kick` - -### 3. 🌍 Bored API - -- `GET https://bored-api.appbrewery.com/random` -- **Command**: `get activity` - -### 4. πŸ” GitHub Users Search API - -- `GET https://api.github.com/search/users?q=john` -- **Command**: `search github john` - -### 5. 🌦️ Weather API (Open-Meteo) - -- `GET https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true` -- **Command**: `get weather Berlin` -- _(You can hardcode city-to-coordinates mapping in app)_ - -## πŸ—ƒοΈ Public API for Simulating Chat Message Storage - -### πŸ”Ή DummyJSON β€” https://dummyjson.com - -Supports: `GET`, `POST`, `PUT`, `DELETE` - -- Use `/comments` or `/posts` as chat message entities -- Good for simulating user-generated message storage - -#### Example: - -| Command | API Action | -| -------------------------------- | --------------------- | -| `send message "Hello!"` | `POST /comments` | -| `edit message 42 "Updated text"` | `PUT /comments/42` | -| `delete message 42` | `DELETE /comments/42` | +![CI/CD Pipeline](./ci-cd.png) diff --git a/ci-cd.png b/ci-cd.png new file mode 100644 index 0000000000000000000000000000000000000000..2b4786d825df4739acf0ad594514d052b603d3fc GIT binary patch literal 66952 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

MI`t;-qR~JqYxc7p!9olLV8p+Mu_^ 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