diff --git a/README.md b/README.md index 578426b..1a3bc5b 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,6 @@ Country Explorer lets you browse European countries, filter by subregion, sort b name or population, and save favorites. Built with React, TypeScript and Vite by team IT2810-H26-T23. -## Requirements - -- Git and access to this repository. -- Node.js **22.20.0** and npm **10.9.3** (the versions used for local verification). -- Internet access to install dependencies and load country data and flags. - -No API key or environment file is needed. Check your installed versions with -`node --version` and `npm --version`. - ## Run locally 1. Clone the repository and enter the application folder: @@ -27,7 +18,6 @@ No API key or environment file is needed. Check your installed versions with 2. Install the locked dependencies: ```bash - npm install npm ci ``` @@ -69,14 +59,21 @@ See the deployment document below for serving the build on the VM. ## Working in this repository -`main` is protected — you cannot push to it directly. Work on a branch and -open a pull request: +Development work is integrated through `dev`. Start each issue branch from an +up-to-date `dev` branch, push the issue branch, and open a pull request into +`dev`: ```bash -git checkout -b my-feature -git push -u origin my-feature +git switch dev +git pull --ff-only +git switch -c "-" +git push -u origin "-" ``` +When the complete project is ready for release, `dev` is merged into `main`. +Both shared branches are protected, so changes reach them through reviewed +merge requests rather than direct pushes. + Before a pull request can be merged: 1. **A member of IT2810-H26-T23 must approve it.** You cannot approve your own pull diff --git a/country-explorer/README.md b/country-explorer/README.md index 089a035..719fa9a 100644 --- a/country-explorer/README.md +++ b/country-explorer/README.md @@ -1,101 +1,39 @@ -# Country Explorer +# Country Explorer application -React + TypeScript + Vite app that fetches and displays country data from a REST API. +This directory contains the React, TypeScript, and Vite application. It fetches +European country data from a REST API and lets users filter, sort, navigate, and +save favorite countries. + +Project-level installation, testing, deployment, and Git workflow instructions +are in the [main README](../README.md). Details about the data source and mapping +are in the [API documentation](./src/api/README.md). ## Project structure -``` +```text src/ -├── api/ # Functions for calling the external REST API (one file per resource) -├── components/ # Reusable UI components, e.g. App.tsx (one file per component) -├── hooks/ # Custom React hooks +├── api/ # REST API client and API documentation +├── components/ # React components, styles, and component tests +├── hooks/ # Custom React and TanStack Query hooks +├── lib/ # Shared library configuration +├── test/ # Shared test setup and fixtures ├── types/ # Shared TypeScript types and interfaces -├── utils/ # Small, framework-agnostic helper functions -├── assets/ # Images, icons, and other static assets +├── utils/ # Storage and sorting helpers +├── assets/ # Images and other bundled assets ├── index.css # Global stylesheet └── main.tsx # Application entry point ``` -`api/`, `hooks/`, `types/`, and `utils/` are created once there is real code to put in them. See [`src/api/README.md`](./src/api/README.md) for which REST API is used, the selected countries, data fields, and error handling. - Naming conventions: -- Components: PascalCase file names matching the default export (e.g. `CountryCard.tsx`), colocated with their own CSS file when they have one (e.g. `App.tsx` + `App.css`). -- Hooks: camelCase file names prefixed with `use` (e.g. `useFetch.ts`). -- API functions & utilities: camelCase file names describing what they do (e.g. `getCountries.ts`, `formatNumber.ts`). -- Types: camelCase file names matching the domain they describe (e.g. `country.ts`). -- Tests are colocated with the file they test (e.g. `CountryCard.test.tsx` next to `CountryCard.tsx`), rather than kept in a separate top-level test folder. - -## About this template - -This project was scaffolded with the React + TypeScript + Vite template, which provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -### React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). +- Components use PascalCase file names matching their default export and keep + their CSS and tests beside the component. +- Hooks use camelCase names beginning with `use`. +- API functions and utilities use descriptive camelCase names. +- Shared domain types use descriptive camelCase file names. -### Expanding the ESLint configuration +## Tooling -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); -``` - -You can also install [eslint-plugin-react-x](https://npmx.dev/package/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://npmx.dev/package/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x'; -import reactDom from 'eslint-plugin-react-dom'; - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); -``` +The app uses React, TanStack Query, TypeScript, Vite, ESLint, Prettier, Vitest, +and Testing Library. The React Compiler is not enabled. Vite's base path is +`/project1/`, matching the deployment path on the group VM. diff --git a/country-explorer/src/api/README.md b/country-explorer/src/api/README.md index baec3d9..3c0c06f 100644 --- a/country-explorer/src/api/README.md +++ b/country-explorer/src/api/README.md @@ -2,10 +2,13 @@ The application uses [countries.dev](https://countries.dev) to fetch country data. It's a free, publicly available API with no API key, sign-up, or authentication required. It's the successor to `apicountries.com`, which now redirects to it, and returns the same field shape as the (now retired) `restcountries.com` v2 API. -## Endpoints used +## Endpoints -- `GET /region/{region}` — fetches every country in a region. Used once to load all European countries: `/region/europe`. -- `GET /alpha/{code}` — fetches a single country by its ISO alpha-2/alpha-3 code. Used when looking up or refreshing one specific country by code. +- `GET /region/{region}` — fetches every country in a region. The application + uses `/region/europe` to load its country list. +- `GET /alpha/{code}` — fetches a single country by its ISO alpha-2 or alpha-3 + code. A client function is implemented for this endpoint, but the current UI + does not call it. ## Selected countries @@ -67,21 +70,21 @@ All 53 entries returned by `/region/europe`: | Ukraine | UA | UKR | | United Kingdom of Great Britain and Northern Ireland | GB | GBR | -This list includes a handful of dependent territories alongside sovereign states (Åland Islands, Faroe Islands, Gibraltar, Guernsey, Isle of Man, Jersey, Svalbard and Jan Mayen), following the API's own region classification. Each country's `independent` field can be used later to filter down to sovereign states only, if that turns out to be desired. +This list includes a handful of dependent territories alongside sovereign states (Åland Islands, Faroe Islands, Gibraltar, Guernsey, Isle of Man, Jersey, Svalbard and Jan Mayen), following the API's own region classification. ## Data fields used -| Field (from the wireframe) | API key(s) | Notes | -| -------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| Name | `name` | | -| Flag | `Mapped to flagUrl, `flags.png` | `flag` also gives an emoji flag | -| Capital | `capital` | | -| Region | `region`, `subregion` | Used by `RegionFilter` | -| Population | `population` | | -| Area | `area` | In km² | -| Currency | `currencies` | | -| Languages | `languages` | | -| — | `alpha2Code`, `Mapped to code for selection, favorites, and lookups` | Not shown in the UI; used internally as a stable id/key and for `/alpha/{code}` lookups | +| UI/internal field | API key(s) | Notes | +| ----------------- | --------------------- | --------------------------------------------------------- | +| Name | `name` | | +| Flag URL | `flags.svg` | Mapped to `flagUrl` | +| Capital | `capital` | | +| Region/filter | `region`, `subregion` | Subregion is preferred by `RegionFilter` | +| Population | `population` | | +| Area | `area` | Displayed in km² | +| Currencies | `currencies` | Names and codes are combined into display strings | +| Languages | `languages` | Language names become display strings | +| Country code | `alpha3Code` | Mapped to `code`; used for selection, favorites, and keys | ## TypeScript models @@ -112,8 +115,15 @@ The API responds with a normal HTTP status code plus a small JSON error body: | `404` | No match — unknown/typo'd code or name | `{ "error": "Country not found" }` | | `429` | Abuse ceiling hit (~60 requests/second per IP; there's no API key or per-key quota) | `{ "error": "Too Many Requests", "message": "Rate limit reached" }` | -A `404` is treated as "no result" in the UI (e.g. an empty/error state), not as a crash, since it normally just means the requested code or name didn't match anything. The app only needs one `/region/europe` call plus occasional `/alpha/{code}` lookups, so the `429` ceiling should never realistically be hit in normal use. +Network failures and non-success HTTP responses, including `404`, are converted +to `ApiError` instances. TanStack Query exposes the error to `App`, which displays +an error message and a retry button. A failed request therefore produces a +recoverable error state rather than crashing the application. Normal UI use only +needs the `/region/europe` request, so the `429` ceiling should not be reached. ## Data storage -Fetched country data is only kept in memory (component state / the data-fetching library's cache) and is never written to `localStorage` or `sessionStorage`. Only user-created state — favorites, selected region, sort option — may be persisted to Web Storage (see `wireframes-and-component-structure.md`). +Fetched country data is only kept in the TanStack Query memory cache and is never +written to `localStorage` or `sessionStorage`. Only user-created state — favorites, +selected region, and sorting option — is persisted to Web Storage. See +[`docs/component-structure.md`](../../../docs/component-structure.md). diff --git a/docs/component-structure.md b/docs/component-structure.md index ad0991a..b8061ae 100644 --- a/docs/component-structure.md +++ b/docs/component-structure.md @@ -1,19 +1,7 @@ # Wireframes and Component Structure -This document describes the planned user interface, component hierarchy, -component responsibilities, props, and state for Country Explorer. - -## Current implementation - -`App` coordinates query results and UI state. `RegionFilter`, `SortSelector`, and -`CountrySelector` reuse `Dropdown`. `CountryCard` contains `FavoriteButton`; -`NavigationControls` and `FavoritesList` receive data and callbacks from `App`. -`Loading` and `ErrorMessage` handle request states. Header, footer, and the controls -section are markup inside `App`, not separate components. `useFavorites` owns -favorite state, while `useCountries` wraps TanStack Query. - -The wireframes and planned hierarchy below describe the initial design; the -current implementation above is the reference for actual component boundaries. +This document describes the initial wireframes and the component structure that +was implemented for Country Explorer. ## Wireframes @@ -21,186 +9,129 @@ current implementation above is the reference for actual component boundaries. ![Desktop wireframe](wireframes/desktop-wireframe.png) -The desktop layout displays the country card and the favorites list next to -each other. Filters and selection controls are displayed above the main -content. +The initial desktop wireframe placed the country card and favorites list next to +each other, with filters and selection controls above them. In the implemented +layout, the controls, selected-country section, and favorites section are stacked +vertically at all viewport widths. The content inside each section adapts to the +available width. ### Mobile ![Mobile wireframe](wireframes/mobile-wireframe.png) -The mobile layout displays all sections in one column. The filters appear -first, followed by the country card, navigation controls, and favorites list. - -These wireframes are initial design sketches. The final appearance may change -during implementation. +The mobile layout displays all sections in one column. The filters appear first, +followed by the country card, navigation controls, and favorites list. -## Country card content +The wireframes are initial design sketches, so they do not represent every final +component boundary or layout choice. -The application displays one country at a time. The country card will contain: - -- Country name -- Flag -- Capital -- Region -- Population -- Area -- Currency -- Languages -- Favorite status -- Previous and next navigation buttons -- Current position in the country selection - -## Component hierarchy +## Current component hierarchy - `App` - - `Header` - - `FilterControls` + - Header markup + - Controls section - `RegionFilter` + - `Dropdown` - `SortSelector` - - `CountrySelector` - - `CountryCard` - - `FavoriteButton` - - `NavigationControls` - - `FavoritesList` - - `Footer` + - `Dropdown` + - `CountrySelector` + - `Dropdown` + - Selected-country section + - `Loading`, `ErrorMessage`, or: + - `CountryCard` + - `FavoriteButton` + - `NavigationControls` + - Favorites section + - `FavoritesList` + - Footer markup + +Header, footer, and the controls section are markup inside `App`, not separate +React components. ## Component responsibilities ### App -The root component of the application. It coordinates the main application -state and passes data and callback functions to child components. - -Responsibilities: - -- Retrieve country data through the country query hook -- Store the currently selected country -- Store the selected region and sorting option -- Store the user's favorite countries -- Produce the filtered and sorted country selection -- Display loading and error states -- Pass data and callbacks to child components +The root component coordinates query results and application state. It: -### Header +- Retrieves European country data through `useCountries` +- Stores the selected country code, region, and sorting option +- Uses `useFavorites` to manage favorite country codes +- Produces the filtered and sorted country lists +- Displays loading, error, empty, and successful states +- Passes data and callbacks to child components -Displays the application title and a short introduction. +### Dropdown -### FilterControls - -Groups the controls used to filter and sort countries. - -Props: - -- Current region -- Current sorting option -- Callback for changing region -- Callback for changing sorting option +Provides the reusable accessible dropdown UI used by all three selection +controls. It displays radio options, manages whether the popup is open, closes +on outside interaction or Escape, and restores focus to its trigger after a +selection. ### RegionFilter -Allows the user to filter countries by region. - -Props: - -- Available regions -- Selected region -- Callback for changing the selected region +Allows the user to filter countries by European subregion. It receives the +available regions, selected region, change callback, and disabled state. ### SortSelector -Allows the user to change the order of the countries. - -Props: - -- Selected sorting option -- Callback for changing the sorting option +Allows the user to order countries alphabetically or by population. It receives +the selected sorting option, change callback, and disabled state. ### CountrySelector -Allows the user to jump directly to a specific country. - -Props: - -- Filtered and sorted countries -- Currently selected country -- Callback for selecting a country +Allows the user to jump directly to a country in the filtered and sorted list. +It receives the available countries, selected country code, selection callback, +and disabled state. ### CountryCard -Displays information about one country at a time. - -Props: - -- Selected country -- Whether the country is a favorite -- Callback for toggling favorite status +Displays one country's name, flag, capital, region, population, area, languages, +currencies, and favorite control. It receives the selected country, its favorite +status, and a callback for toggling that status. ### FavoriteButton -Allows the user to add or remove the displayed country from favorites. - -Props: - -- Favorite status -- Callback for toggling favorite status -- Country name for an accessible button label +Adds or removes the displayed country from favorites. It receives the favorite +status, country name for its accessible label, and toggle callback. ### NavigationControls -Allows the user to move to the previous or next country. - -Props: - -- Current country position -- Number of available countries -- Callback for showing the previous country -- Callback for showing the next country -- Information about whether navigation buttons should be disabled +Displays the current position and moves to the previous or next country. It +receives the current index, number of available countries, and navigation +callbacks. It determines when the navigation buttons must be disabled. ### FavoritesList -Displays the countries saved as favorites. Selecting a favorite displays that -country in the main country card. - -Props: - -- Favorite countries -- Callback for selecting a favorite country - -### Footer +Displays countries saved as favorites. Selecting one clears the active region +filter and displays that country in the main country card. -Displays brief information about the REST API used by the application. +### Loading and ErrorMessage -## State ownership +`Loading` displays a status while country data is being fetched. `ErrorMessage` +displays the request error and provides a retry action. -The main application state will initially be stored in `App`. +## State ownership and storage -| State | Purpose | Storage | -| ------------------ | -------------------------------------------- | ------------------------------ | -| Selected country | Determines which single country is displayed | React state | -| Selected region | Determines which countries are included | React state and sessionStorage | -| Sorting option | Determines the order of countries | React state and sessionStorage | -| Favorite countries | Stores the user's favorite selections | React state and localStorage | +| State | Owner/source | Persistence | +| ---------------------- | -------------------- | ---------------- | +| Selected country code | `App` | None | +| Selected region | `App` | `sessionStorage` | +| Sorting option | `App` | `sessionStorage` | +| Favorite country codes | `useFavorites` | `localStorage` | +| Country data | TanStack Query cache | Memory only | -The country data itself will be fetched from the REST API with TanStack Query. -API data will not be stored in localStorage or sessionStorage. - -Child components receive the data they need through props. Callback functions -are also passed through props when a child component needs to update state -owned by `App`. +The storage helpers namespace their keys with `country-explorer:`. Invalid +stored sorting and favorite values fall back safely. An unknown stored region +produces an empty result until the user selects a valid region or "All regions". ## Displaying one country at a time -Only one country will be displayed in `CountryCard` at any given time. - -The user can change the displayed country by: - -- Selecting the previous country -- Selecting the next country -- Choosing a country from `CountrySelector` -- Selecting a country from `FavoritesList` +Only one country is displayed in `CountryCard` at a time. The user can change it +with the previous and next buttons, `CountrySelector`, or `FavoritesList`. -Filtering or sorting may change the available countries and their order. If -the selected country is no longer included after filtering, the first -available country will be selected. +Filtering or sorting can change the available countries and their order. The +selected country remains selected when it is still available. If filtering +removes it, the first country in the resulting list is selected. Selecting a +favorite clears the region filter first so favorites from other subregions can +be displayed. diff --git a/docs/git-workflow.md b/docs/git-workflow.md index f96251d..94f5ad0 100644 --- a/docs/git-workflow.md +++ b/docs/git-workflow.md @@ -1,6 +1,6 @@ # Git workflow -Work on an issue branch and open a pull request into protected `dev`. +Work on an issue branch and open a pull request into `dev`. Use the branch template `-`. Replace all placeholders below before running the commands. @@ -30,3 +30,9 @@ Closes # A different member of IT2810-H26-T23 must approve the PR; approvals from staff do not count. Resolve every review conversation before merging. New commits dismiss previous approval, so request another review after making changes. + +## Releasing the completed project + +Issue branches are merged into `dev` throughout development. When the complete +project is ready for release, open a final merge request from `dev` into `main`. +Do not use `main` as the target for individual issue branches. diff --git a/docs/testing.md b/docs/testing.md index 584b96a..4cb368e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,32 +1,22 @@ # Testing -From `country-explorer/`, run `npm test`, `npm run lint`, and `npm run build`. -Use `npm run format:check` to check formatting. Tests use fixed country fixtures -and mock API calls; storage is cleared between tests. +We verified the application from `country-explorer/` with `npm test`, +`npm run lint`, `npm run format:check`, and `npm run build`. All checks passed. +The test suite contained 45 passing tests. The tests cover country rendering, loading/error states, filtering, sorting, navigation, dropdown selection, and storage/restoration, including invalid data. -The current checkout has no committed snapshot test or `.snap` file; snapshot -coverage is therefore not claimed here. +`CountryCard.test.tsx` also contains a snapshot test, with its committed snapshot +stored in `src/components/__snapshots__/CountryCard.test.tsx.snap`. ## Browser and device coverage The group's [usability evaluation](usability-evaluation.md) reports that the app worked as expected and identifies scrolling on mobile as an improvement area. The [deployment report](deployment.md) also records functional browser checks. -The group confirms testing on a PC and an iPhone. Browser names/versions, the -PC operating system, and the iPhone model/iOS version were not recorded. -Portrait and landscape coverage was not specified. jsdom tests do not establish -cross-browser coverage. +The group confirms testing on a PC and an iPhone. | Device | Reported result | | ------ | ---------------------------------------------------------- | | PC | Functionality worked as expected in the group's evaluation | | iPhone | Functionality worked; navigation required some scrolling | - -For future checks, record browser/version, device/OS, orientation, and results. - -Check filtering, sorting, favorites after reload, navigation, keyboard focus and -selection, and layout without horizontal scrolling. Test both portrait and -landscape on small screens. This is a checklist for future records, not a claim -that every combination has already been verified.