Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
35a0a8a
feat: add Next.js to the project
Quiddlee Nov 23, 2023
0c31cf3
refactor: change folder
Quiddlee Nov 23, 2023
74f98f1
refactor: clear readme
Quiddlee Nov 23, 2023
05c5ce6
refactor: remove unused actions
Quiddlee Nov 23, 2023
7d7be38
feat: add pull request template
Quiddlee Nov 23, 2023
eb53c04
feat: change app layout
Quiddlee Nov 23, 2023
5c5866d
refactor: remove unused App component
Quiddlee Nov 23, 2023
8aac75a
refactor: replace app title with constant
Quiddlee Nov 23, 2023
38a859d
refactor: change scroll to use context
Quiddlee Nov 23, 2023
1b80190
feat: implement movie list server side rendering
Quiddlee Nov 24, 2023
190549a
feat: implement movie details server side rendering
Quiddlee Nov 24, 2023
e44deb2
feat: implement preserving query between routes
Quiddlee Nov 24, 2023
54c8609
fix: tooltip issue
Quiddlee Nov 24, 2023
1b67ecc
fix: minor issue
Quiddlee Nov 24, 2023
968079f
feat: change tooltip font size
Quiddlee Nov 24, 2023
f96ec82
feat: remove unused logic
Quiddlee Nov 24, 2023
3a7829a
feat: move search state to url
Quiddlee Nov 24, 2023
b8a9cae
feat: move movies per page state to url
Quiddlee Nov 24, 2023
02c1ae3
refactor: change to delete empty query
Quiddlee Nov 24, 2023
2466737
refactor: remove unused code
Quiddlee Nov 24, 2023
1eb01d3
feat: add loader between routes
Quiddlee Nov 24, 2023
024daa6
refactor: remove use client definition
Quiddlee Nov 25, 2023
9d8339d
fix: to use server side rendering on query change
Quiddlee Nov 25, 2023
2950fa2
fix: default query
Quiddlee Nov 25, 2023
c2c0ca9
fix: app layout test
Quiddlee Nov 25, 2023
e35d7b8
fix: background issues
Quiddlee Nov 25, 2023
8271f59
fix: to handle movie not found error
Quiddlee Nov 25, 2023
baf9278
feat: add mock star rating
Quiddlee Nov 25, 2023
9256a50
fix: movie details alignment
Quiddlee Nov 26, 2023
b53f68b
chore: add view transition lib
Quiddlee Nov 26, 2023
0d23cf1
feat: add view transition animations
Quiddlee Nov 26, 2023
90a7658
refactor: change file structure
Quiddlee Nov 26, 2023
7f00b4e
feat: add error test
Quiddlee Nov 26, 2023
931bf8a
refactor: add width and height to Image
Quiddlee Nov 26, 2023
f652624
refactor: change tests to work with next js
Quiddlee Nov 26, 2023
726d40e
fix: image error
Quiddlee Nov 26, 2023
07815a1
refactor: remove unused test
Quiddlee Nov 26, 2023
c26d804
refactor: change movie tests to work with next js
Quiddlee Nov 26, 2023
3e201e4
refactor: change movie tests to work with next js
Quiddlee Nov 26, 2023
26b1e7c
refactor: change all tests to work with next js
Quiddlee Nov 26, 2023
09c47fd
feat: add more unit tests
Quiddlee Nov 26, 2023
898d00b
feat: add more unit tests
Quiddlee Nov 26, 2023
e76a537
refactor: fix minor issue
Quiddlee Nov 26, 2023
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
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VITE_API_URL=https://www.omdbapi.com/
VITE_API_KEY=dbb72d83
NEXT_PUBLIC_API_URL=https://www.omdbapi.com/
NEXT_PUBLIC_API_KEY=dbb72d83
17 changes: 6 additions & 11 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'prettier',
'next/core-web-vitals'
],
ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts', 'prettier.config.js', '@typescript-eslint', 'node_modules', 'tailwind.config.js'],
plugins: ['react', 'react-refresh', 'simple-import-sort', 'import', 'prettier'],
ignorePatterns: ['dist', '.eslintrc.cjs', 'prettier.config.js', 'node_modules', 'tailwind.config.js', 'next.config.js', 'environment.d.ts'],
plugins: ['react', 'react-refresh', 'simple-import-sort', 'import', 'prettier', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
Expand All @@ -34,14 +35,7 @@ module.exports = {
'no-void': 0,
'no-param-reassign': 0,
'import/no-extraneous-dependencies': 0,
'import/extensions': [
'error',
'ignorePackages',
{
'ts': 'always',
'tsx': 'always'
}
],
'import/extensions': 0,
'import/namespace': 0,
'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
'import/order': [
Expand All @@ -60,7 +54,7 @@ module.exports = {
position: 'before',
},
{
pattern: '@src/**',
pattern: '*',
group: 'internal',
},
],
Expand All @@ -77,6 +71,7 @@ module.exports = {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
node: {
extensions: ['.js','.jsx','.ts','.tsx']
Expand Down
21 changes: 0 additions & 21 deletions .github/actions/ci-setup/action.yml

This file was deleted.

25 changes: 25 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
1. Task: [click](https://github.com/rolling-scopes-school/tasks/tree/master/react/modules/module04)
2. Screenshot:
3. Deploy: [click](https://quiddlee.github.io/Cinemania/)
4. Done 14.11.2023 / deadline 20.11.2023
5. Score: 100 / 100

## Functional requirements 🍿
- [x] 📥 Redux is integrated to the app with the help of Redux Toolkit - 25 points
- [x] 🔍 Search is saved in the store - 5 points
- [x] 🔢 Items per page is saved in the store - 5 points
- [x] 🪟 View mode is saved in the store - 10 points
- [x] 🔃 Loading indicators are shown, loading flags are saved in the store, - 10 points
- [x] 📞 When either search or items per page is changed, application makes a new call using RTK Query to fetch the data - 25 points
- [x] 🧪 Tests had been modified to test the functionality using Redux and RTK Query - 20 points

## Penalties 👎
- [ ] 😥 TypeScript isn't used: -95 points
- [ ] 😱 Usage of any: -20 points per each
- [ ] 🙀 Usage of ts-ignore: -20 points per each
- [ ] 🧃 Direct DOM manipulations inside the React components: -50 points per each
- [ ] 🪝 React hooks are used to get access to either state, or to the component lifecycle: -70 points
- [ ] 💨 Presence of code-smells (God-object, chunks of duplicate code), commented code sections: -10 points per each
- [ ] 📚 Usage of component libraries, e.g. Material UI, Ant Design: -100 points
- [ ] 📔 Test coverage is less than 80%: -30 points
- [ ] 📛 Commits after the deadline: -40 points
29 changes: 0 additions & 29 deletions .github/workflows/deploy.yml

This file was deleted.

57 changes: 34 additions & 23 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
# Logs
logs
*.log
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
.idea

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
coverage

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#*.env

# local env files
#.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# react-components
# Cinemania 🍿
13 changes: 13 additions & 0 deletions app/model/slice.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, it } from 'vitest';

import { appReducer, dataFetched, initialState } from './slice';

describe('appSlice', () => {
it("should change app's slice dataFetched state", () => {
const state = appReducer(initialState, dataFetched(true));

expect(state).toEqual({
isFetching: true,
});
});
});
23 changes: 23 additions & 0 deletions app/model/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface IInitialState {
isFetching: boolean;
}

export const initialState: IInitialState = {
isFetching: false,
};

export const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
dataFetched: (state, action: PayloadAction<boolean>) => {
state.isFetching = action.payload;
},
},
});

export const { dataFetched } = appSlice.actions;

export const appReducer = appSlice.reducer;
10 changes: 6 additions & 4 deletions src/app/store/store.ts → app/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';

import { movieApi } from '../../entities/movie/api/movieApi.ts';
import { searchReducer } from '../../features/Search/model/slice.ts';
import { appReducer } from '../model/slice.ts';
import { movieApi } from '@entities/movie/api/movieApi';
import { createWrapper } from 'next-redux-wrapper';

import { appReducer } from '../model/slice';

export type AppStore = ReturnType<typeof setupStore>;
export type RootState = ReturnType<typeof rootReducer>;
Expand All @@ -11,7 +12,6 @@ export type PreloadState = Partial<RootState>;

const rootReducer = combineReducers({
[movieApi.reducerPath]: movieApi.reducer,
searchReducer,
appReducer,
});

Expand All @@ -22,3 +22,5 @@ export const setupStore = (preloadedState?: PreloadState) =>
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(movieApi.middleware),
});

export const wrapper = createWrapper<AppStore>(() => setupStore());
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
73 changes: 73 additions & 0 deletions entities/movie/Movie.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import * as useGetMovieList from '@shared/hooks/useGetMovieList';
import renderWithRouter from '@test/helpers/RenderWithRouter';
import { mockMovieItem, mockMovies } from '@test/mocks/data';
import { afterEach, describe, expect, it, vi } from 'vitest';

import Movie from './ui/Movie';

const mockedUseGetMovieList = vi.spyOn(useGetMovieList, 'default');

describe('Movie', () => {
afterEach(() => void vi.clearAllMocks());

it('should render the relevant movie data', () => {
renderWithRouter(
<Movie
onMouseMove={vi.fn()}
onMouseOut={vi.fn()}
data={mockMovieItem}
delay={0}
/>,
);

const poster = screen.getByTestId<HTMLImageElement>('movie-poster');
const title = screen.getByTestId('movie-title');
const year = screen.getByTestId('movie-year');

const mockPosterPath = mockMovieItem.Poster.slice(
mockMovieItem.Poster.lastIndexOf('/') + 1,
);
const match = poster.src.match(mockPosterPath);
const renderedPath = match?.at(0);

expect(poster).toBeInTheDocument();
expect(title).toBeInTheDocument();
expect(year).toBeInTheDocument();

expect(renderedPath).toBe(mockPosterPath);
expect(title).toHaveTextContent(mockMovieItem.Title);
expect(year).toHaveTextContent(mockMovieItem.Year);
});

it('should open a detailed card component when clicking on a card', async () => {
mockedUseGetMovieList.mockReturnValue({
movieList: mockMovies,
totalResults: mockMovies.length,
});

const router = renderWithRouter(
<Movie
data={mockMovieItem}
delay={0}
onMouseMove={vi.fn()}
onMouseOut={vi.fn()}
/>,
);

const movie = screen.getByTestId('movie-item');
await userEvent.click(movie);

expect(router.push).toHaveBeenCalledWith(
`/${mockMovieItem.imdbID}`,
`/${mockMovieItem.imdbID}`,
{
locale: undefined,
scroll: true,
shallow: undefined,
},
);
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { ReactNode } from 'react';

import { renderHook, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { describe, it } from 'vitest';

import { useGetMovieListQuery, useGetMovieQuery } from './movieApi.ts';
import { setupStore } from '../../../app/store/store.ts';
import {
useGetMovieListQuery,
useGetMovieQuery,
} from '@entities/movie/api/movieApi';
import {
DEFAULT_PAGE,
MOVIES_PER_PAGE,
QUERY_FALLBACK,
} from '../../../shared/const/const.ts';
import { mockMovieDetails, mockMovies } from '../../../test/mocks/data.ts';
} from '@shared/const/const';
import { mockMovieDetails, mockMovies } from '@test/mocks/data';
import { Provider } from 'react-redux';
import { describe, expect, it } from 'vitest';

import { setupStore } from '../../../app/store/store';

function wrapper({ children }: { children: ReactNode }) {
const store = setupStore();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { IInitialState } from '../../../features/Search/types/types.ts';
import rootApi from '../../../shared/api/rootApi.ts';
import { QUERY_FALLBACK } from '../../../shared/const/const.ts';
import {
ApiErrorResponse,
ApiMovieListResponse,
ApiMovieResponse,
} from '../../../shared/types/types.ts';
} from '@customTypes/types';
import { IInitialState } from '@features/Search/types/types';
import rootApi from '@shared/api/rootApi';
import { QUERY_FALLBACK } from '@shared/const/const';

interface IMovieListParams {
query: IInitialState['query'];
Expand All @@ -17,13 +17,13 @@ export const movieApi = rootApi.injectEndpoints({
endpoints: (build) => ({
getMovieList: build.query<ApiMovieListResponse, IMovieListParams>({
query: ({ query, page, moviesPerPage }) =>
`?apikey=${import.meta.env.VITE_API_KEY}&s=${
`?apikey=${process.env.NEXT_PUBLIC_API_KEY}&s=${
query || QUERY_FALLBACK || moviesPerPage
}&page=${page}`,
}),

getMovie: build.query<ApiErrorResponse | ApiMovieResponse, string>({
query: (id) => `?apikey=${import.meta.env.VITE_API_KEY}&i=${id}`,
query: (id) => `?apikey=${process.env.NEXT_PUBLIC_API_KEY}&i=${id}`,
}),
}),
});
Expand Down
Loading