Skip to content

🥋 A Clothing Store serverless application with fully tested store functionality using React, Redux + Sagas, Cloud Firestore, Jest, etc.

Notifications You must be signed in to change notification settings

SKorchinskiy/Clothing-Store

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clothing Store

Table of Contents

Project Description

🥋 A Clothing Store serverless application with fully tested store functionality. The project was initially built using JavaScript, however was rewritten for TypeScript usage. The application is written using:

  • React for components
  • React-Router-Dom for page routing
  • Redux for state management
  • Redux-Saga for asynchronous side effects (API calls)
  • Cloud Firestore as a data storage
  • Stripe as a payment service
  • Jest and Testing Library as tools for testing User Interface for consistency

Note

Strpe service is used in test mode, thus it has no effect paying for products. It was made like that due to the fact that I don't have real products to sell.

Project Structure

📦 Clothing-Store
├─ .gitignore
├─ README.md
├─ netlify
│  └─ functions
│     └─ create-payment-intent.js
├─ package-lock.json
├─ package.json
├─ public
│  ├─ _redirects
│  ├─ favicon.ico
│  ├─ index.html
│  ├─ logo192.png
│  ├─ logo512.png
│  ├─ manifest.json
│  ├─ mockServiceWorker.js
│  └─ robots.txt
├─ src
│  ├─ App..jsx
│  ├─ assets
│  │  ├─ crown.svg
│  │  └─ shopping-bag.svg
│  ├─ components
│  │  ├─ button
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ button.component.test.js.snap
│  │  │  │  └─ button.component.test.js
│  │  │  ├─ button.component.tsx
│  │  │  └─ button.styles.tsx
│  │  ├─ cart-dropdown
│  │  │  ├─ __tests__
│  │  │  │  └─ cart-dropdown.component.test.js
│  │  │  ├─ cart-dropdown.component.tsx
│  │  │  └─ cart-dropdown.styles.tsx
│  │  ├─ cart-icon
│  │  │  ├─ __tests__
│  │  │  │  └─ cart-icon.component.test.js
│  │  │  ├─ cart-icon.component.tsx
│  │  │  └─ cart-icon.styles.tsx
│  │  ├─ cart-item
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ cart-item.component.test.js.snap
│  │  │  │  ├─ cart-item.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ cart-item.stub.js
│  │  │  ├─ cart-item.component.tsx
│  │  │  └─ cart-item.styles.tsx
│  │  ├─ category-list
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ category-list.component.test.js.snap
│  │  │  │  ├─ category-list.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ category-list.stub.js
│  │  │  ├─ category-list.component.tsx
│  │  │  └─ category-list.styles.tsx
│  │  ├─ category-preview
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ category-preview.test.js.snap
│  │  │  │  ├─ category-preview.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ category-preview.stub.js
│  │  │  ├─ category-preview.component.tsx
│  │  │  └─ category-preview.styles.tsx
│  │  ├─ category
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ category.component.test.js.snap
│  │  │  │  ├─ category.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ category.stub.js
│  │  │  ├─ category.component.tsx
│  │  │  └─ category.styles.tsx
│  │  ├─ dropdown-item
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ dropdown-item.component.test.js.snap
│  │  │  │  ├─ dropdown-item.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ dropdown-item.stub.js
│  │  │  ├─ dropdown-item.component.tsx
│  │  │  └─ dropdown-item.styles.tsx
│  │  ├─ form-input
│  │  │  ├─ form-input.component.tsx
│  │  │  └─ form-input.styles.tsx
│  │  ├─ loader
│  │  │  ├─ loader.component.tsx
│  │  │  └─ loader.styles.tsx
│  │  ├─ payment-form
│  │  │  ├─ payment-form.component.tsx
│  │  │  └─ payment-form.styles.tsx
│  │  ├─ product-item
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ product-item.component.test.js.snap
│  │  │  │  ├─ product-item.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ product-item.stub.js
│  │  │  ├─ product-item.component.tsx
│  │  │  └─ product-item.styles.tsx
│  │  ├─ sign-in-form
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ sign-in-form.component.test.js.snap
│  │  │  │  ├─ sign-in-form.component.test.js
│  │  │  │  └─ stubs
│  │  │  │     └─ sign-in-user.stub.js
│  │  │  ├─ sign-in-form.component.tsx
│  │  │  └─ sign-in-form.styles.tsx
│  │  └─ sign-up-form
│  │     ├─ __tests__
│  │     │  ├─ __snapshots__
│  │     │  │  └─ sign-up-form.component.test.js.snap
│  │     │  ├─ sign-up-form.component.test.js
│  │     │  └─ stubs
│  │     │     └─ sign-up-user.stub.js
│  │     ├─ sign-up-form.component.tsx
│  │     └─ sign-up-form.styles.tsx
│  ├─ configs
│  │  ├─ __mocks__
│  │  │  └─ firebase.config.ts
│  │  └─ firebase.config.ts
│  ├─ custom.d.ts
│  ├─ hooks
│  │  └─ useResolution.hook.tsx
│  ├─ index.css
│  ├─ index.js
│  ├─ layout
│  │  ├─ __tests__
│  │  │  └─ Layout.test.js
│  │  ├─ header
│  │  │  ├─ __tests__
│  │  │  │  ├─ __snapshots__
│  │  │  │  │  └─ header.component.test.js.snap
│  │  │  │  └─ header.component.test.js
│  │  │  ├─ header.component.tsx
│  │  │  └─ header.styles.tsx
│  │  └─ layout.component.tsx
│  ├─ mocks
│  │  ├─ handlers.js
│  │  └─ mock-service-worker.js
│  ├─ pages
│  │  ├─ auth
│  │  │  ├─ auth.page.tsx
│  │  │  └─ auth.styles.tsx
│  │  ├─ category
│  │  │  ├─ category.page.tsx
│  │  │  └─ category.styles.tsx
│  │  ├─ checkout
│  │  │  ├─ checkout.page.tsx
│  │  │  └─ checkout.styles.tsx
│  │  ├─ home
│  │  │  └─ home.page.tsx
│  │  └─ shop
│  │     ├─ shop.page.tsx
│  │     └─ shop.styles.tsx
│  ├─ redux
│  │  ├─ actions
│  │  │  ├─ cart
│  │  │  │  ├─ cart.action.ts
│  │  │  │  └─ cart.type.ts
│  │  │  ├─ categories
│  │  │  │  ├─ categories.action.ts
│  │  │  │  └─ categories.type.ts
│  │  │  ├─ category
│  │  │  │  ├─ category.action.ts
│  │  │  │  └─ category.type.ts
│  │  │  ├─ create-action.helper.ts
│  │  │  └─ user
│  │  │     ├─ user.action.ts
│  │  │     └─ user.type.ts
│  │  ├─ reducers
│  │  │  ├─ cart
│  │  │  │  └─ cart.reducer.ts
│  │  │  ├─ categories
│  │  │  │  └─ categories.reducer.ts
│  │  │  ├─ category
│  │  │  │  └─ category.reducer.ts
│  │  │  ├─ root.reducer.ts
│  │  │  └─ user
│  │  │     └─ user.reducer.ts
│  │  ├─ sagas
│  │  │  ├─ __tests__
│  │  │  │  └─ user.saga.test.js
│  │  │  ├─ categories.saga.ts
│  │  │  ├─ category.saga.ts
│  │  │  ├─ root.saga.ts
│  │  │  └─ user.saga.ts
│  │  ├─ selectors
│  │  │  ├─ cart.selector.ts
│  │  │  ├─ categories.selector.ts
│  │  │  ├─ category.selector.ts
│  │  │  └─ user.selector.ts
│  │  └─ store
│  │     └─ store.ts
│  ├─ routes
│  │  └─ index.js
│  ├─ service-worker.js
│  ├─ serviceWorkerRegistration.js
│  └─ utils
│     ├─ __mocks__
│     │  ├─ categoryFactory.js
│     │  └─ firebase.utils.ts
│     ├─ firebase.utils.ts
│     ├─ stripe.utils.js
│     └─ test.utils.js
└─ tsconfig.json

©generated by Project Tree Generator

Project Workflow

The project is deployed using Netlify. You can check it here. Netlify was chosen due to its availability and being free of charge to some limit.

Application Workflow

  • Authentication: when using the application, you can sign up or sign in to it. If you prefer manually entering the credentials, they will be saved to the cloud firestore. It is required to save the credentials, so you can sign in whenever you want without registering each time. However, manually entering credentials for each service we use is tiring. Therefore, you can sign in using Google provider. A new window will be created where you'll need to choose your google account for service usage. You can checkout this page here.

Authentication Page Screenshot

  • Main page: After signing in or just being on the main page (being authenticated isn't mandatory for the application), you'll see all the categories of products that the clothing store provides. You can checkout this page here.

Main Page Screenshot

  • Specific Category Page: By clicking on a category on the main page, you'll be redirected to the chosen category page. You can checkout this page here.

Mens Category Screenshot

  • Product Cart: After hovering on the desired product, you can add it to the cart, which contains all the chosen products in specific amount. (It's located in the upper right corner)

Product Cart Screenshot

  • Shop Page: On the shop page limited amount of each category products is gathered. You can checkout this page here

Shop Page Screentshot

  • Checkout Page: Through the cart you can get to the checkout page, where chosen products are listed. You can checkout this page here

Checkout Page Screeshot

Project Optimizations

  • Web vitals

Here are measurements made with PageSpeed Insights. You can check detailed results here.

PC Performance Screenshot

Mobile Performance Screenshots

Tip

As you can see, efficiency isn't great for mobile version. It can be impoved by:

  • storing images of different sizes in the data storage. This way the amount of data transfered via network is less
  • using server side rendering. By using this strategy, the server is responsible for rendering, not client's device
  • using CDN + Redis. It can significantly improve performance as product images are the same for each user, so the caching will be efficient
  • Different screen resolutions

You've previously seen screenshots of the applications made from laptop. Now, I want to demonstrate how the app looks from mobile.

Note

The application is created as a progressive web app, so it can be added on the main screen of your device

Mobile Application Layout Home Page Mobile Application Layout Shop Page Mobile Application Layout Auth Page Mobile Application Layout Product Cart Mobile Application Layout Checkout Page

  • Partial Rendering

Each page is loaded using lazy loading strategy. The main idea is that the content is loaded not simultaneously from the begining, but by chunks. So the needed page is loaded only when it is needed. More about lazy loading you can read here.

Code from the application given below. You can check it out here.

const Home = lazy(() => import("../pages/home/home.page"));
const Auth = lazy(() => import("../pages/auth/auth.page"));
const Shop = lazy(() => import("../pages/shop/shop.page"));
const Checkout = lazy(() => import("../pages/checkout/checkout.page"));
const Category = lazy(() => import("../pages/category/category.page"));

Also, during page loading the loader is shown

Loader Screenshot

Why did I build this project ?

I built Clothing Store project to improve my skills in web development. This application is the first relatively big application where I've gained lots of skills and knowledge. In the next section I've listed what I've learned during the project building process.

What did I learn ?

  • Technologies: React, Redux, Redux-Saga, React-Router-Dom, Jest, Testing Library, Cloud Firestore, HTML, CSS
  • Strategies and Concepts: lazy loading, serverless functions, page routing, device resolution adaptation, progressive web applications, web vitals optimization, client side rendering, partial rendering, loaders, service workers

About

🥋 A Clothing Store serverless application with fully tested store functionality using React, Redux + Sagas, Cloud Firestore, Jest, etc.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published