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
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/indent': ['error', 2],
'@typescript-eslint/indent': "off",
'@typescript-eslint/ban-types': ['error', {
extendDefaults: true,
types: {
Expand Down
23 changes: 23 additions & 0 deletions .github/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Github Page Deploy Workflow

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: npm ci
- run: npm run build
- name: Deploy
uses: crazy-max/ghaction-github-pages@v1
with:
target_branch: gh-pages
build_dir: build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ Show `input:search` in the header when a page contains a `ProductList` to search
1. Save the `Search` value in the URL as a `?query=value` to apply on page load.
2. Show `There are no phones/tablets/accessories/products matching the query` instead of `ProductList` when needed.
3. Add `debounce` to the search field.
- Replace `<your_account>` with your Github username in the [DEMO LINK](https://maximtsyrulnyk.github.io/react_phone-catalog/ ) and add it to the PR description.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<link rel="icon" type="image/png" href="/favicon.png" />
</head>
<body>
<div id="root"></div>
Expand Down
26 changes: 22 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"devDependencies": {
"@cypress/react18": "^2.0.1",
"@mate-academy/scripts": "^1.8.5",
"@mate-academy/scripts": "^2.1.3",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^20.14.10",
Expand All @@ -36,6 +36,7 @@
"eslint-plugin-react": "^7.34.4",
"eslint-plugin-react-hooks": "^4.6.2",
"gh-pages": "^6.1.1",
"husky": "^9.1.7",
"mochawesome": "^7.1.3",
"mochawesome-merge": "^4.3.0",
"mochawesome-report-generator": "^6.2.0",
Expand Down Expand Up @@ -79,5 +80,20 @@
"_comment": "Add `cypressComponents: true` to enable component tests",
"cypress": true
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
],
"*.tsx": [
"eslint --fix",
"prettier --write"
]
}
}
Binary file added public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/banner-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/banner-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/banner-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/banner-4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 19 additions & 1 deletion src/App.scss
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
// not empty
@import './styles/reset';
@import './styles/fonts';
@import './styles/variables';
@import './styles/mixins';
@import './styles/typography';


.App {
display: flex;
flex-direction: column;
}

html,
body {
background-color: var(--page-bg);
color: var(--text-main);

transition: background-color 0.3s ease, color 0.3s ease;
}
66 changes: 65 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,71 @@
import { HashRouter, Route, Routes } from 'react-router-dom';
import './App.scss';
import { HomePage } from './modules/HomePage';
import { NotFoundPage } from './modules/NotFoundPage';
import { Layout } from './components/Layout';
import { ThemeProvider } from './context/ThemeContext';
import { CartPage } from './modules/CartPage';
import { CartProvider } from './context/CartContext';
import { FavouritesProvider } from './context/FavoritesContext';
import { FavouritesPage } from './modules/FavouritesPage';
import { CategoryPage } from './modules/CategoryPage';
import { CategoriesType, PathType } from './types/Types';
import { ProductDetailsPage } from './modules/ProductDetailsPage';

export const App = () => (
<div className="App">
<h1>Product Catalog</h1>
<ThemeProvider>
<CartProvider>
<FavouritesProvider>
<HashRouter>
<Routes>
<Route path={PathType.HOME} element={<Layout />}>
<Route index element={<HomePage />} />

<Route
path={PathType.PHONES}
element={
<CategoryPage
title={'Mobile phones'}
category={CategoriesType.PHONES}
/>
}
></Route>
<Route
path={PathType.TABLETS}
element={
<CategoryPage
title={'Tablets'}
category={CategoriesType.TABLETS}
/>
}
></Route>
<Route
path={PathType.ACCESSORIES}
element={
<CategoryPage
title={'Accessories'}
category={CategoriesType.ACCESSORIES}
/>
}
></Route>

<Route
path={PathType.FAVOURITES}
element={<FavouritesPage />}
></Route>
<Route path={PathType.CART} element={<CartPage />}></Route>
<Route
path="/product/:productId"
element={<ProductDetailsPage />}
/>

<Route path="*" element={<NotFoundPage />}></Route>
</Route>
</Routes>
</HashRouter>
</FavouritesProvider>
</CartProvider>
</ThemeProvider>
</div>
);
11 changes: 11 additions & 0 deletions src/api/fetchClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const BASE_URL = 'api';

export async function getData<TData>(url: string): Promise<TData> {
const response = await fetch(`${BASE_URL}${url}`);

if (!response.ok) {
throw new Error(`Error fetching data: ${response.statusText}`);
}

return response.json();
}
67 changes: 67 additions & 0 deletions src/api/products.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
CatalogProducts,
CategoriesType,
PathType,
Product,
} from '../types/Types';
import { getData } from './fetchClient';

export const getProducts = () => {
return getData<CatalogProducts[]>(`${PathType.PRODUCTS}.json`);
};

export const getPhones = async () => {
const products = await getProducts();

return products.filter(product => product.category === CategoriesType.PHONES);
};

export const getTablets = async () => {
const products = await getProducts();

return products.filter(
product => product.category === CategoriesType.TABLETS,
);
};

export const getAccessories = async () => {
const products = await getProducts();

return products.filter(
product => product.category === CategoriesType.ACCESSORIES,
);
};

export const getProductById = async (category: string, itemId: string) => {
let path = '';

switch (category) {
case CategoriesType.PHONES:
path = PathType.PHONES;
break;
case CategoriesType.TABLETS:
path = PathType.TABLETS;
break;
case CategoriesType.ACCESSORIES:
path = PathType.ACCESSORIES;
break;
default:
throw new Error('Unknown category');
}

const products = await getData<Product[]>(`${path}.json`);

const product = products.find(item => item.id === itemId);

if (!product) {
throw new Error('Product not found');
}

return product;
};

export const getSuggestedProducts = async () => {
const products = await getProducts();

return [...products].sort(() => Math.random() - 0.5).slice(0, 12);
};
25 changes: 25 additions & 0 deletions src/assets/logo/logo-d.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/assets/logo/logo-l.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions src/components/Breadcrumbs/Breadcrumbs.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@import '../../styles/variables';
@import '../../styles/mixins';
@import '../../styles/typography';

.breadcrumbs {
margin-top: 24px;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 8px;
grid-column: 1 / -1;

@include respond-to('tablet') {
margin-bottom: 40px;
}

&__home {
display: flex;
align-items: center;
flex-shrink: 0;
color: var(--text-main);

@include hover-transition(color);

@include hover {
color: var(--icon-color);
}
}

&__link {
@include small-text;

font-weight: 600;
color: var(--text-main);
text-decoration: none;

@include hover-transition(color);

@include hover {
color: var(--text-main);
}

&::first-letter {
text-transform: uppercase;
}
}

&__separator {
display: flex;
align-items: center;
flex-shrink: 0;
color: var(--icon-color);
}

&__link,
&__current {
@include small-text;

font-weight: 600;
color: var(--text-secondary);
line-height: 1;
}
}
Loading