Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
extends: [
'airbnb',
'airbnb-typescript',
'prettier',
'plugin:react/recommended',
],
plugins: ['@typescript-eslint', 'prettier', 'react-hooks'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
jest: true,
},
rules: {
'react/react-in-jsx-scope': 'off',
'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }],
'import/prefer-default-export': 'off',
'react/function-component-definition': 'off',
'react/require-default-props': 'off',
},
ignorePatterns: ['build/', 'node_modules/', '**/*.js', '!.eslintrc.js'],
};
45 changes: 45 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Deploy to GitHub Pages

on:
push:
branches: [ main ]

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Start mock API
run: npm run mock-api &

- name: Wait for mock API to start
run: sleep 5

- name: Run linting
run: |
npm run lint
npm run stylelint

- name: Run tests with coverage
run: npm run test:coverage

- name: Build project
run: npm run build

- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: build
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5",
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "auto"
}
13 changes: 13 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": ["stylelint-config-standard"],
"rules": {
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"]
}
],
"declaration-block-trailing-semicolon": null,
"no-descending-specificity": null
}
}
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,76 @@ Jasper
Notes

```

## Solution

This solution is implemented using React with TypeScript and follows SOLID principles with a separation of concerns inspired by MVC (Model-View-Controller) architecture.

### Architecture

The application is structured as follows:

#### Model
- `CatDataSource.ts` - Handles data fetching from the API using Axios
- Defines data interfaces and provides methods to fetch data

#### Controller
- `CatController.ts` - Processes the data from the model
- Filters pet data to get only cats
- Groups cats by owner gender
- Sorts cats alphabetically

#### View
- `CatList.tsx` - Main component that orchestrates data fetching and rendering
- `GenderSection.tsx` - Component that renders a gender section with its cats list

### Technologies Used

- React for the UI
- TypeScript for type safety
- Axios for API calls
- Tailwind CSS for styling

### SOLID Principles Application

1. **Single Responsibility Principle**
- Each class has a single responsibility:
- `CatDataSource` is responsible for data fetching
- `CatController` is responsible for data processing
- View components are responsible for rendering UI

2. **Open/Closed Principle**
- Components are designed to be extended without modification
- New view components can be added without changing existing code

3. **Liskov Substitution Principle**
- Components use interfaces for type definitions, allowing for polymorphism

4. **Interface Segregation Principle**
- Small, focused interfaces are used

5. **Dependency Inversion Principle**
- High-level modules don't depend on low-level modules directly
- Dependencies are injected (e.g., CatDataSource into CatController)

### How to Run

1. Clone the repository
2. Install dependencies
```bash
npm install
```
3. Start the development server
```bash
npm start
```
4. Open your browser and navigate to http://localhost:3000

### Building for Production

To build the application for production:
```bash
npm run build
```

This will create a `build` directory with optimized production build.
86 changes: 86 additions & 0 deletions mockoon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"uuid": "e207626e-e42f-4642-ac74-7e212dcd6b92",
"lastMigration": 27,
"name": "Cats API Mock",
"endpointPrefix": "",
"latency": 0,
"port": 3001,
"hostname": "0.0.0.0",
"folders": [],
"routes": [
{
"uuid": "5e1c6c3e-8ced-4e49-8b97-24b5a4b91d0e",
"documentation": "Returns the list of people with their pets",
"method": "get",
"endpoint": "people.json",
"responses": [
{
"uuid": "38778b3a-7c0c-4f2b-9d9d-5212b3e45ff5",
"body": "[{\"name\":\"Bob\",\"gender\":\"Male\",\"age\":23,\"pets\":[{\"name\":\"Garfield\",\"type\":\"Cat\"},{\"name\":\"Fido\",\"type\":\"Dog\"}]},{\"name\":\"Jennifer\",\"gender\":\"Female\",\"age\":18,\"pets\":[{\"name\":\"Garfield\",\"type\":\"Cat\"}]},{\"name\":\"Steve\",\"gender\":\"Male\",\"age\":45,\"pets\":null},{\"name\":\"Fred\",\"gender\":\"Male\",\"age\":40,\"pets\":[{\"name\":\"Tom\",\"type\":\"Cat\"},{\"name\":\"Max\",\"type\":\"Cat\"},{\"name\":\"Sam\",\"type\":\"Dog\"},{\"name\":\"Jim\",\"type\":\"Cat\"}]},{\"name\":\"Samantha\",\"gender\":\"Female\",\"age\":40,\"pets\":[{\"name\":\"Tabby\",\"type\":\"Cat\"}]},{\"name\":\"Alice\",\"gender\":\"Female\",\"age\":64,\"pets\":[{\"name\":\"Simba\",\"type\":\"Cat\"},{\"name\":\"Nemo\",\"type\":\"Fish\"}]}]",
"latency": 0,
"statusCode": 200,
"label": "Success",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
],
"filePath": "",
"sendFileAsBody": false,
"rules": [],
"rulesOperator": "OR",
"disableTemplating": false,
"fallbackTo404": false,
"default": true,
"databucketID": "",
"bodyType": "INLINE"
}
],
"enabled": true,
"responseMode": null
}
],
"rootChildren": [
{
"type": "route",
"uuid": "5e1c6c3e-8ced-4e49-8b97-24b5a4b91d0e"
}
],
"proxyMode": false,
"proxyHost": "",
"proxyRemovePrefix": false,
"tlsOptions": {
"enabled": false,
"type": "CERT",
"pfxPath": "",
"certPath": "",
"keyPath": "",
"caPath": "",
"passphrase": ""
},
"cors": true,
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"proxyReqHeaders": [
{
"key": "",
"value": ""
}
],
"proxyResHeaders": [
{
"key": "",
"value": ""
}
],
"data": []
}
Loading