Overview
A single-page clone of the Amazon storefront, built in mid-2022 to practise state management against a real backend rather than local fixtures. Visitors sign in with Google, browse a product catalogue held in Cloud Firestore, and add, update and remove items in a persistent cart whose totals are recalculated on every change.
The project is an exercise rather than a shop: there is no payment step, and the checkout button is intentionally inert. The interesting part is how the client keeps Redux state and Firestore in step.
Architecture
- Client only. A Create React App build (customised with
react-app-rewiredfor webpack 5 polyfills) on React 18.2 and React Router 6.3. - Data: Cloud Firestore with two collections,
productandcart-items, accessed through a small helper module (getCollection,getDocument,addDocument,updateDocumentQuantity,deleteDocument). - Auth: Firebase Authentication with the Google provider (
signInWithPopup). Unauthenticated users are routed to the login screen; the signed-in user is cached client-side. - Hosting and CI/CD: Firebase Hosting with a single-page rewrite to
index.html. GitHub Actions builds and deploys on merge tomain, and a second workflow publishes a preview channel for every pull request.
State management
Redux Toolkit holds three slices: product, cart and user. Every cart and product operation is a createAsyncThunk that calls the Firestore helpers, so components only dispatch intents and read selectors. Cart totals (quantity × price, summed) are derived in the fulfilled reducer of the cart fetch, which keeps components free of arithmetic. A later refactor ("redux rework to reduce fetchs") added a dedicated product slice and moved more of the Firestore access into thunks, so components stopped fetching on their own.
Routes are / (catalogue), /cart and /login. Styling uses styled-components, with react-toastify for feedback and react-number-format for currency display.
What I'd change today
- Await every write. Several thunks fire Firestore writes without awaiting them, so a failed write can leave Redux optimistic and wrong. I'd await, roll back on error and surface the failure.
- Stop writing on read. The collection helper normalises document IDs on every fetch, which costs a write per document per read. IDs belong in the document at creation time.
- Replace
console.warnwith real error handling and add tests around the thunks with the Firestore emulator. - Add a checkout behind a hosted payment page (for example Stripe Checkout), so card data never touches the client.
Built with
- React 18, React Router 6, Redux Toolkit, React Redux
- Firebase 9: Authentication (Google) and Cloud Firestore
- styled-components, react-toastify
- Firebase Hosting, GitHub Actions (merge deploys and PR previews)