Skip to content
Open
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_CAT_API_KEY=YOUR_CAT-API-KEY
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5"
}
123 changes: 110 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,118 @@
# GlobalWebIndex Engineering Challenge
<!--
Based on: https://github.com/sokratisvs/Best-README-Template
-->

## Exercise: CatLover
<div align="center">
<h1>Cat Lovers React App 🐾</h1>
<p>
A React 19 + Vite app for cat lovers, powered by <a href="https://thecatapi.com/">TheCatAPI</a>.
Browse random cats, explore breeds, and save your favourites.
</p>
</div>

Create a React application for cat lovers which is going to build upon thecatapi.com and will have 3 views.
The **first** view displays a list of 10 random cat images and a button to load more. Clicking on any of those images opens a modal view with the image and the information about the cat’s breed if available. This would be a link to the second view below - the breed detail. The modal should also contain a form to mark the image as your favourite (a part of the third view as well). Make sure you can copy-paste the URL of the modal and send it to your friends - they should see the same image as you can see.
---

The **second** view displays a list of cat breeds. Each breed opens a modal again with a list of cat images of that breed. Each of those images must be a link to the image detail from the previous point.
## Table of Contents

The **third** view allows you do the following things:
- [About the Project](#about-the-project)
- [Features](#features)
- [Built With](#built-with)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Environment Variables](#environment-variables)
- [Available Scripts](#available-scripts)
- [Usage](#usage)
- [View 1 – Random Cats](#view-1--random-cats)
- [View 2 – Cat Breeds](#view-2--cat-breeds)
- [View 3 – Favourites](#view-3--favourites)
- [Project Structure](#project-structure)
- [API Reference](#api-reference)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [Acknowledgements](#acknowledgements)

- Display your favourite cats
- Remove an image from your favourites (use any UX option you like)
---

You can find the API documentation here: https://developers.thecatapi.com/
We give you a lot of freedom in technologies and ways of doing things. We only insist on you using React.js. Get creative as much as you want, we WILL appreciate it. You will not be evaluated based on how well you follow these instructions, but based on how sensible your solution will be. In case you are not able to implement something you would normally implement for time reasons, make it clear with a comment.
## About the Project

## Submission
This project is a small web application for cat lovers built on top of **TheCatAPI**.
It demonstrates modern React patterns (React 19, React Query, Vite) and client-side routing with URL-shareable modals.

Once you have built your app, share your code in the mean suits you best
Good luck, potential colleague!
The app provides:

- A **gallery of random cats**
- A **breed explorer** with image galleries per breed
- A **favourites page** where users can manage their favourite cat images

### Features

**View 1 – Random Cats**

- Displays a list of **10 random cat images**
- **“Load more”** button to fetch and append more random cats
- Clicking an image opens a **modal** with:
- Larger cat image
- **Breed information** if available
- A **form/button to mark the image as favourite**
- **Deep linking**: the modal has a unique URL so you can copy-paste and share it; opening that URL shows the same image and its details.

**View 2 – Cat Breeds**

- Displays a **list of cat breeds** (name + basic info).
- Selecting a breed opens a **modal** containing:
- A list/gallery of images for that breed
- Each image is a link to the **image detail modal** from View 1 (same URL behavior).

**View 3 – Favourites**

- Displays **all favourite cat images** saved by the user.
- Allows **removing an image from favourites** (e.g. via button, icon, or context menu).
- Shares the same image detail modal behavior as in View 1.

### Built With

- [Vite](https://vitejs.dev/) – build tool & dev server
- [React 19](https://react.dev/) – UI library
- [React Router](https://reactrouter.com/) (or similar) – routing & URL-based modals
- [React Query](https://tanstack.com/query/latest) – data fetching & caching
- [pnpm](https://pnpm.io/) – package manager
- [TypeScript](https://www.typescriptlang.org/) (optional but recommended)
- [TheCatAPI](https://thecatapi.com/) – cat images & breed data

---

## Getting Started

Follow these steps to run the project locally.

### Prerequisites

**Node.js** (LTS recommended)

- **pnpm**

```bash
npm install -g pnpm
```

**Or for Node 18+**

```bash

corepack enable
```

- **Local development**

```
# Move into the new project folder
cd cat-lover-app

# Install dependencies
pnpm install

# Start dev server
pnpm dev
```
22 changes: 22 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
23 changes: 23 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cat-lover-app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
46 changes: 46 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "cat-lover-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@tanstack/react-query": "^5.90.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.553.0",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.5",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@tailwindcss/vite": "^4.1.17",
"@types/node": "^24.6.0",
"@types/react": "^19.1.16",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.36.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.22",
"globals": "^16.4.0",
"tailwindcss": "^4.1.17",
"typescript": "~5.9.3",
"typescript-eslint": "^8.45.0",
"vite": "^7.1.7"
}
}
Loading