This project simulates a game catalog by consuming the RAWG API for providing data visualization similar to how actual game stores do.
- Dependencies
- Design System
- Project Structure
- Assets
- Fonts
- Data
- Context
- [Root Context]
- Local Storage
- [Local Db]
- Requests
- [Data Request Params]
- Types
- [Added By Status]
- [Data Service Dictionary]
- [Developer]
- [Esrb Rating]
- [Game]
- [Game Commons]
- [Games Platform]
- [Genre]
- [Image]
- [index]
- [Local Db Store]
- [Parent Platform]
- [Platform]
- [Publisher]
- [Rating]
- [Recommended]
- [Screenshots]
- [Short Screenshot]
- [Store]
- [Tag]
- Context
- Middlewares
- [Api Middleware]
- Services
- [Api Service]
- [Game Service]
- [Genre Service]
- [Platform Service]
- [Publishe Service]
- [Tag Service]
- Utils
- [Local Db Utils]
- [Parser Utils]
- [Startup Utils]
- [Styling Utils]
- [Type Utils]
- Views
- Components
- [Footer]
- [Gallery]
- [Game Card]
- [Game Panel]
- [Header]
- [Image Card]
- [Search Filter]
- [Selection]
- Pages
- [Error Page]
- Routes
- [Game]
- [Home]
- [Root]
- [Search]
- Components
- Assets
This project relies directly on the following dependencies:
- axios: ^1.7.7
- react: ^18.3.1
- react-dom: ^18.3.1
- react-router-dom: ^6.26.2
- @eslint/js: ^9.9.0
- @types/react: ^18.3.3
- @types/react-dom: ^18.3.0
- @typescript-eslint/eslint-plugin: ^8.7.0
- @typescript-eslint/parser: ^8.7.0
- @vitejs/plugin-react: ^4.3.1
- eslint: ^9.11.1
- eslint-plugin-react-hooks: ^5.1.0-rc.0
- eslint-plugin-react-refresh: ^0.4.9
- globals: ^15.9.0
- sass: ^1.79.3
- typescript: ^5.5.3
- typescript-eslint: ^8.0.1
- vite: ^5.4.1
- vite-tsconfig-paths: ^5.0.1
This section provides information regarding the visual aspects of the project's layout.
A simple prototype as built using Figma for providing a general look of how the application should be implemented. It can be found on the project's prototype link
Three base colors were chosen for building up the whole user interface. Along with these base colors, many tones for each one were provided for applying transitions, animations, gradients and so on. This palette was generated using the Coolors utility.
$white: hsla(32, 100%, 97%, 1);
$white-d: hsla(34, 37%, 93%, 1);
$white-d1: hsla(34, 23%, 88%, 1);
$white-d2: hsla(34, 17%, 84%, 1);
$white-d3: hsla(32, 12%, 79%, 1);
$white-d4: hsla(37, 10%, 74%, 1);
$white-d6: hsla(35, 8%, 70%, 1);
$cyan: hsla(179, 78%, 52%, 1);
$cyan-d: hsla(179, 72%, 49%, 1);
$cyan-d1: hsla(179, 73%, 47%, 1);
$cyan-d2: hsla(179, 73%, 45%, 1);
$cyan-d3: hsla(179, 73%, 42%, 1);
$cyan-d4: hsla(179, 73%, 40%, 1);
$cyan-d5: hsla(179, 74%, 37%, 1);
$black: #221e22ff;
$black-d: #1d1a1dff;
$black-d1: #181518ff;
$black-d2: #131113ff;
$black-d3: #0d0c0dff;
$black-d4: #080808ff;
$black-d5: #030303ff;
These colors can be visualized in the pictures below:
The entire project typography relies on the RedHat Display Font only, provided under the SIL Open Font license. All rights reserved.
This section covers the logical aspects and implementations of the project through its different layers, for making it work as the expected result.
The Game Mania project's file structure is provided as follows:
Assets are static resources that can be used for multiple purposes within the project. These may include either images, fonts, source text documents etc.
The @src/data
namespace is meant for containing pieces of software that are
dominant
in the aspects of working with data. It is the namespace containing either
static data models, like types or interfaces, or data operations such as
context, for
global state management, and database abstractions.
The @data/context
module is responsible for providing global state
managements for data that is shared between various UI components of the
system.
The existing contexts in the project are:
The RootContext
is responsible for providing common state management
across the whole application, providing data that is likely to be used
within any existing UI component, not restricted to a given component or an
application route, page or similar.
The data provided by the RootContext
is defined within the IRootContext
interface:
interface IRootContext {
games?: Game[]
genres?: Genre[]
platforms?: Platform[]
publishers?: Publisher[]
selectedGame?: Game
setGames: Dispatch<SetStateAction<Game[] | undefined>>
setGenres: Dispatch<SetStateAction<Genre[] | undefined>>
setPlatforms: Dispatch<SetStateAction<Platform[] | undefined>>
setPublishers: Dispatch<SetStateAction<Publisher[] | undefined>>
setSelectedGame: Dispatch<SetStateAction<Game | undefined>>
setTags: Dispatch<SetStateAction<Tag[] | undefined>>
tags?: Tag[],
apiMiddleware?: IApiMiddleware
}
The @data/local-storage
module is responsible for providing client-side
database operations. This module actually relies on the IndexedDB
API
implementation of browsers, and is likely to be described as an abstraction
layer for simplifying access to the API.
The main representation of the module is the LocalDb
class, which is an
implementation of the ILocalDb
interface, providing a set of abstractions for
working more effectively with the IndexedDB
API.
This abstraction was required due to the fact that the IndexedDB
API was
specified within an era where Javascript Promise
s didn't exist and, due to
such context, it relies on many callbacks for each internal request for the
database. Dealing with that API directly is not recommended, and may lead to
inconsistencies within the application.
The ILocalDb
interface defines the behaviors implemented by the LocalDb
class, as follows:
interface ILocalDb<T> {
openObjectStore(storageName: string,
mode: IDBTransactionMode
): Promise<IDBObjectStore>
create<T extends ApiData[]>(storages: { [K in keyof T]: LocalDbStore<T[K]> }): void
getObjectById(storageName: string, key: number): Promise<T>
getAll(storageName: string, params?: DataRequestParams): Promise<T[]>
addObject(storageName: string, object: T): Promise<boolean>
addBulk(storageName: string, objects: T[]): Promise<ApiData[]>
removeObject(storageName: string, key: keyof T): Promise<boolean>
updateObject(storageName: string,
key: keyof T,
newObject: T
): Promise<boolean>
isCreated(): boolean
reset(): void
}
Request parameters define the structure of the query parameters sent to the API
for retrieving data.
These parameters are defined within the @data/requests
module.
At the moment, the existing request parameters are:
The DataRequestParams
type defines the overall structure of the query
parameters sent to the API for retrieving data.
It is not meant for production, given most of its properties are only valid for
the games
endpoint of the API, thus it is likely to be changed in the future.
type DataRequestParams = {
page?: number
pageSize?: number
search?: string
searchPrecise?: boolean
searchExact?: boolean
parentPlatforms?: string
platforms?: string
stores?: string
developers?: string
publishers?: string
genres?: string
tags?: string
creators?: string
dates?: string
updated?: string
platformsCount?: number
metacritic?: string
excludeCollection?: number
excludeAdditions?: boolean
excludeParents?: boolean
excludeGameSeries?: boolean
excludeStores?: string
ordering?: 'name' | 'released' | 'added' | 'created' | 'updated' | 'rating' | 'metacritic'
}
The @data/types
module is responsible for providing the data models used
within the project. These are meant to match either data provided directly by
API endpoints or internal data structures of such data.
The existing types in the project are, in alphabetical order:
- Added By Status
- Data Service Dictionary
- Developer
- Esrb Rating
- Game
- Game Commons
- Games Platform
- Genre
- Image
- Local Db Store
- Parent Platform
- Platform
- Publisher
- Rating
- Recommended
- Screenshots
- Short Screenshot
- Store
- Tag
The AddedByStatus
type defines the structure of the added_by_status
property
from games
endpoint responses.
type AddedByStatus = {
yet: number
owned: number
beaten: number
toplay: number
dropped: number
}
The DataServiceDictionary
type defines the structure of the dictionary used
for implementing the dynamic behavior of the ApiService
class.
type DataServiceDictionary = {
games: IGameService
genres: IDataService<Genre>
platforms: IDataService<Platform>
publishers: IDataService<Publisher>
tags: IDataService<Tag>
}
The Developer
type defines the structure of the developer
property from
games
endpoint responses.
type Developer = {
id: number
name: string
slug: string
gamesCount: number
imageBackground: string
}
The EsrbRating
type defines the structure of the esrb_rating
property from
games
endpoint responses.
type EsrbRating = {
id: number
name: string
slug: string
}
The Game
type defines the structure of the games/{id}
endpoint responses.
type Game = GameCommons & {
nameOriginal: string
description: string
metacriticPlatforms: unknown[]
backgroundImageAdditional: string
website: string
screenshotsCount: number
moviesCount: number
creatorsCount: number
achievementsCount: number
parentAchievementsCount: number
redditUrl: string
redditName: string
redditDescription: string
redditLogo: string
redditCount: number
twitchCount: number
youtubeCount: number
alternativeNames: string[]
metacriticUrl: string
parentsCount: number
additionsCount: number
gameSeriesCount: number
developers: Developer[]
publishers: Publisher[]
descriptionRaw: string
shortScreenshots?: ShortScreenshot[]
}
The GameCommons
type defines the common structure of the games
endpoint
shared across both games
and games/{id}
responses.
type GameCommons = {
rating: number
ratingTop: number
ratings: Rating[]
added: number
addedByStatus: AddedByStatus
playtime: number
released: string
tba: boolean
updated: string
backgroundImage: string
id: number
slug: string
name: string
metacritic: unknown
reviewsTextCount: number
ratingsCount: number
suggestionsCount: number
userGame: unknown
reviewsCount: number
saturatedColor: string
dominantColor: string
parentPlatforms: ParentPlatform[]
platforms: GamesPlatform[]
stores: Store[]
genres: Genre[]
tags: Tag[]
esrbRating: EsrbRating
clip: unknown
}
The GamesPlatform
type defines the structure of records of data within the
platforms
property from
games
endpoint responses.
type GamesPlatform = {
platform: Platform
releasedAt: string
requirements: unknown
}
The Genre
type defines the structure of or more records of the genres
endpoint responses.
type Genre = {
id: number
name: string
slug: string
gamesCount: number
imageBackground: string
}
The Image
type defines the structure of one or more data records within the
results
property from games/{id}/screenshots
endpoint responses.
type Image = {
id: number
image: string
hidden: boolean
width: number
height: number
}
The LocalDbStore
type defines the structure of the options used for creating
object stores within the LocalDb
class.
type LocalDbStore<T> = {
name: string
keyPath: keyof T
autoIncrement?: boolean
indices: StoreIndex<T>[]
}
The ParentPlatform
type defines the structure of one or more data records of
the parent_platforms
property from games
endpoint responses.
export type ParentPlatform = {
platform: {
id: number
name: string
slug: string
}
}
The Platform
type defines the structure of the platforms
endpoint responses.
type Platform = {
id: 1
name: string
slug: string
image: string
yearEnd: unknown
yearStart: unknown
gamesCount: number
imageBackground: string
}
The Publisher
type defines the structure of either one or more data records of
the publishers
endpoint responses or the publishers
property from games
endpoint responses.
type Publisher = {
id: number
name: string
slug: string
gamesCount: number
imageBackground: string
}
The Rating
type defines the structure of the ratings
property from games
type Rating = {
count: number
id: number
percent: number
title: string
}
The Recommended
type defines the structure of the project's recommendations
system
type Recommended = {
recent: Game
daily: Game
recentScreenshots: Screenshots
dailyScreenshots: Screenshots
}
The Screenshots
type defines the structure of the games/{id}/screenshots
endpoint responses.
type Screenshots = {
count: number
next?: string
previous?: string
results: Image[]
}
The ShortScreenshot
type defines the structure of the short_screenshots
property from games/{id}
endpoint responses.
type ShortScreenshot = {
id: number
image: string
}
The Store
type defines both the structure of one or more data records of the
stores
property from games
endpoint responses and the stores
endpoint
responses.
type Store = {
id: number
url: string
store: {
id: number
name: string
slug: string
domain: string
gamesCount: number
imageBackground: string
}
}
The StoreIndex
type defines the structure of the indices used for creating
object stores within the LocalDb
class.
type StoreIndex<T> = {
index: keyof T
options: IDBIndexParameters
}
The Tag
type defines the structure of both one or more data records of the
tags
property from games
endpoint responses and the tags
endpoint
responses.
type Tag = {
id: number
name: string
slug: string
language: string
gamesCount: number
imageBackground: string
}
The @src/middlewares
module is responsible for providing middleware functions
for the application.
The existing middlewares in the project are:
The ApiMiddleware
class is responsible for providing a middleware for
intercepting API requests and responses, providing a set of behaviors for
selecting between data provided locally or from the API.
The behavior of the ApiMiddleware
class is defined within the IApiMiddleware
interface:
export interface IApiMiddleware {
getAll(route: keyof DataServiceDictionary,
params?: DataRequestParams
): Promise<ApiData[]>
getById(route: keyof DataServiceDictionary, id: number): Promise<ApiData>
getRecommendations(): Promise<Recommended>
}
The @src/services
module is responsible for providing services that handle
more logical aspects for the application, such as API connections.
The existing services in the project are:
The ApiService
class is responsible for providing a service for providing the
actual API connection for the application.
The behavior of the ApiService
class is defined within the IApiService
interface:
interface IApiService {
createRouteUrl(route: string): string
gameApi: AxiosInstance
}
The DataService
implementations are responsible for providing a service for
handling data operations for the application.
The behavior of a DataService
class is defined within the IDataService
interface:
interface IDataService<T> {
getAll(params: DataRequestParams): Promise<T[]>
getById(id: number): Promise<T>
}
The GameService
class is an implementation of IDataService
responsible for
providing a service for handling Game
data operations for the application. Game
data is provided by the games
endpoint of the API.
The behavior of the GameService
class is defined within the IGameService
,
extending the IDataService
interface:
interface IGameService extends IDataService<Game> {
getScreenshots(id: number): Promise<Screenshots>
}
The GenreService
class is an implementation of IDataService
responsible for
providing a service for handling Genre
data operations for the application.
Genre
data is provided by both the genres
endpoint of the API or as a property
of the games
endpoint response.
The PlatformService
class is an implementation of IDataService
responsible
for providing a service for handling Platform
data operations for the application.
Platform
data is provided by both the platforms
endpoint of the API or as a property of the games
endpoint response.
The @src/utils
module is responsible for providing utility functions for the application. These functions are meant to be used for providing common behaviors across the application, or functionalities that are not directly related to the business logic of the application.
The existing utilities in the project are:
The LocalDbUtils
class is responsible for providing utility functions for the
LocalDb
class.
The behavior of the LocalDbUtils
class is defined within the ILocalDbUtils
interface:
interface ILocalDbUtils {
filterObjects(idbCursorRequest: IDBRequest,
resolve: (value: (ApiData[] | PromiseLike<ApiData[]>)) => void,
results: ApiData[],
params?: DataRequestParams
): void
}
The ParserUtils
class is responsible for providing utility functions for
parsing data either from or to the API. These functions are meant for providing
a more consistent behavior for the application, as the API may provide data in
different formats.
The behavior of the ParserUtils
class is defined within the IParserUtils
interface:
interface IParserUtils {
mapToCamelCase(data: any): unknown
mapToSnakeCase(data: any): unknown
toCamelCase(snakeText: string): string
toSnakeCase(camelText: string): string
}
The StartupUtils
class is responsible for providing utility functions for
starting up the application. These functions are meant for setting up the initial state of the application, such as initializing the database, etc.
The behavior of the StartupUtils
class is defined within the IStartupUtils
interface:
interface IStartupUtils {
initializeDb(db: LocalDb): void
}
The StylingUtils
class is responsible for providing utility functions for the
styling of the application. These functions are meant for providing a more flexible way of styling the application, as some styles may be dependent on the application's state, which can't be defined statically.
The behavior of the StylingUtils
class is defined within the IStylingUtils
interface:
interface IStylingUtils {
getInlineBanner(game?: Game): InlineBannerStyle
}
The TypeUtils
class is responsible for providing utility functions for the type system of the application. These functions are meant for providing a more consistent behavior for the application, as the type system may be complex and require some additional behaviors for working effectively.
The behavior of the TypeUtils
class is defined within the ITypeUtils
interface:
interface ITypeUtils {
mapToGame(data: any): Game
mapToStores(stores: Store[]): Store[]
mapToGamesPlatforms(platforms: GamesPlatform[]): GamesPlatform[]
}
The @src/views
module is responsible for providing the views of the application. These views are meant for providing the actual user interface of the application, and are composed of TSX functional components.
The views are divided into three main categories:
- Components: Reusable components that can be used across multiple pages.
- Pages: Components that represent a single page of the application.
- Routes: Components that represent a single route of the application.
The @src/views/components
module is responsible for providing reusable components that can be used across multiple pages of the application. These are highly dependent on the application's state, meaning that they make no sense by themselves, but only when used within a page/route.
The existing components in the project are:
The @src/views/pages
module is responsible for providing components that represent a single page of the application. These components are independent of the application's state, meaning that they can be used by themselves at any given route.
The existing pages in the project are:
The @src/views/routes
module is responsible for providing components that represent a single route of the application. These components are the main containers for both components and pages, and are meant for providing the actual user interface of the application. Routes are highly dependent on the application's state, but different from components, they are meant for dealing directly with the application's state, usually by using React's context API.
The existing routes in the project are: