From 680f49b1c855b32941a92bdabd5df213dccd92b7 Mon Sep 17 00:00:00 2001 From: Subham Nayak Date: Thu, 2 Jul 2026 12:27:43 +0530 Subject: [PATCH 1/4] Create CONTRIBUTING.md with project guidelines Added a comprehensive contributing guide for the Geeta University MerchStore project, detailing team roles, branching strategy, commit conventions, API contract process, testing, deployment, and code style. --- CONTRIBUTING.md | 231 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e3d3117 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,231 @@ +# Contributing to Geeta University MerchStore + +Welcome! This doc explains how our 4-member team works together on this repo — +ownership, branching, commit/PR conventions, the API contract process, weekly +merge schedule, setup, testing, and deployment. Read this fully before opening +your first PR. + +--- + +## Team & Ownership + +| Member | Role | Owns | +|--------|------|------| +| **M1** | Backend Lead — Auth, Models, Products | `server/config/`, `server/models/` (all 6: User, Product, Order, Cart, Coupon, Review), `server/middleware/`, `server/validators/authValidator.js`, `server/validators/productValidator.js`, `controllers/authController.js`, `controllers/productController.js`, `routes/authRoutes.js`, `routes/productRoutes.js`, `utils/generateToken.js`, `utils/cloudinaryUpload.js`, `server.js`, `.env.example` | +| **M2** | Backend — Transactions, Payments & Real-Time | `controllers/{cart,order,payment,coupon,review,analytics}Controller.js`, `routes/{cart,order,payment,coupon,review,analytics}Routes.js`, `validators/{cart,order,review}Validator.js`, `services/{stripe,email,inventory}Service.js`, `cron/lowStockCron.js`, `socket/orderSocket.js`, `utils/analyticsHelpers.js` | +| **M3** | Frontend — Customer UI & State | `client/src/app/store.js`, `features/{auth,cart,products,orders}/`, customer `pages/` (Home, Login, Register, ProductList, ProductDetail, Cart, Checkout, OrderConfirm, UserDashboard), shared `components/`, `hooks/`, `utils/api.js`, `App.jsx`, `main.jsx`, Tailwind config | +| **M4** | Frontend Admin + DevOps | `client/src/pages/admin/`, `components/admin/`, `components/{ReviewList,ReviewForm,CouponInput}.jsx`, `features/admin/`, `routes/AdminRoutes.jsx`, `postman/`, `docs/`, `.github/workflows/`, `.github/pull_request_template.md` | + +**Golden rule:** only edit files inside your owned folders. If you need a change in +someone else's file (e.g. a schema tweak in M1's model, or adding a section to M3's +`ProductDetail.jsx`), open a PR against *their* file, keep the diff minimal and +clearly marked, and request their review — never push directly to it. + +--- + +## Branching Strategy + +``` +main ← production-ready, tagged releases only +└── dev ← integration branch, always runnable + ├── feature/m1-auth-products + ├── feature/m2-transactions + ├── feature/m3-frontend + └── feature/m4-admin-integration +``` + +- **Sub-feature branches** are encouraged inside your own scope, e.g. + `feature/m1-models`, `feature/m1-auth`, `feature/m1-cloudinary`, + `feature/m2-stripe`, `feature/m2-sockets`. Merge these into your own + `feature/mX-*` branch first, then that branch merges into `dev`. +- **`main` branch protection:** requires 2 PR approvals + CI (ESLint + Jest) pass. +- **`dev` branch protection:** requires 1 PR approval + CI pass. +- **Merge strategy:** squash-merge `feature/*` → `dev`; regular merge commit + `dev` → `main`. + +--- + +## Commit & PR Conventions + +**Commit / PR title format:** +``` +[M] : +``` +Types: `feat`, `fix`, `chore`, `test`, `docs`, `refactor` + +Examples: +- `[M1] feat: add Product model with size/stock schema` +- `[M2] fix: prevent double stock decrement on Stripe retry` +- `[M3] feat: add ProductList filter sidebar` +- `[M4] feat: add RevenueChart to AdminAnalytics` + +**Every PR must include (see `.github/pull_request_template.md`):** +- [ ] What changed and why +- [ ] Linked API contract row(s), if applicable +- [ ] Screenshots/GIF for UI changes +- [ ] `npm run lint` passes with 0 errors, Prettier formatted +- [ ] Tests added/updated, `npm test` passes +- [ ] No edits outside your owned folders (or explicit sign-off from the file's owner) +- [ ] `.env.example` updated if new env vars were introduced + +--- + +## API Contract Process + +Before any backend endpoint is consumed by the frontend, it must be documented and +agreed in the shared **MerchStore API Contract** doc (Notion/Google Sheet): + +`Endpoint | Method | Request Body (JSON) | Response Shape (JSON) | Status Codes | Owner (M1/M2) | Consumer (M3/M4) | Status (Draft/Agreed/Implemented)` + +**Mandatory checkpoints:** + +1. **Week 1 — Full Draft Contract** + - M1 documents: Auth endpoints, Product endpoints (request/response shapes, error formats) + - M2 documents: Cart, Order, Payment, Coupon, Review, Analytics endpoints + - M3/M4 review and flag any missing fields needed by UI + - All 4 sign off — no changes to agreed fields without a PR discussion + +2. **Week 3 — Auth + Product Contract Locked** + - M1 deploys auth routes to `dev`; M3 tests against live API + - Field mismatch → raise a GitHub Issue, M1 fixes within 24hrs + - Response envelope `{ success, data, message, pagination }` strictly enforced + +3. **Week 4 — Cart + Order Contract Locked** + - M2 deploys cart/order routes; M3 tests Cart page and Checkout flow + - Stripe `clientSecret` field name confirmed (`data.clientSecret`) + - Socket event names confirmed: `ORDER_STATUS_UPDATED`, `PAYMENT_CONFIRMED` + +4. **Week 5 — Admin + Analytics Contract Locked** + - M2 deploys analytics routes; M4 connects charts + - M1 confirms admin product endpoints; M4 connects product form modal + - Review endpoint fields confirmed: `rating` (1–5 int), `comment` (string) + +**Breaking Change Policy** — any breaking change to an agreed endpoint requires: +1. A GitHub Issue labeled `breaking-change` +2. 24-hour notice in the team channel +3. PR review by the consuming member (M3 or M4) before merge +4. A version bump on the route (e.g. `/api/v2/...`) if the change cannot be + made backward-compatible + +--- + +## Weekly Checkpoint Merge Schedule + +| Week | Event | Branches Merging | +|------|-------|-------------------| +| 1 end | API contract document finalized & signed off by all 4 | No code merge yet | +| 2 end | M1 models + auth scaffolding ready | M1 merges `feature/m1-models` sub-branch → `dev` | +| 3 end | M1 Auth APIs + M3 React scaffold + Redux ready | M1 auth routes → `dev`; M3 scaffold → `dev` | +| 4 end | M1 Product APIs + M2 Cart APIs + M3 Product pages ready | All three → `dev` (coordinate sequentially, 1hr window) | +| 5 end | M2 Orders/Stripe/WebSocket + M3 Checkout + M4 Admin Products | Merge M2, M3 checkout, M4 admin → `dev` | +| 6 end | M2 Analytics/Coupons/Reviews + M4 Admin Orders/Analytics | Merge M2 and M4 remaining features → `dev` | +| 7 end | Deployment complete; all features on `dev` | `dev` → `main` (pre-release merge) | +| 8 end | Final QA, README, tag release | Tag `v1.0.0` on `main` | + +--- + +## Local Setup + +```bash +git clone +cd merchstore + +# backend +cd server +cp .env.example .env # fill in your own keys (Mongo, JWT, Google OAuth, Cloudinary, Stripe, SMTP) +npm install +npm run dev + +# frontend +cd ../client +cp .env.example .env # VITE_API_BASE_URL, VITE_STRIPE_PUBLISHABLE_KEY, VITE_SOCKET_URL, VITE_GOOGLE_CLIENT_ID +npm install +npm run dev +``` + +Backend runs on `http://localhost:5000`, frontend on `http://localhost:5173` +(Vite proxies `/api` to the backend — see `vite.config.js`). Never commit real +`.env` files — only `.env.example` is tracked in git. + +--- + +## Testing + +- **Backend (M1/M2):** Jest + Supertest. Target ≥70% coverage on auth, model + validation, order, and payment controllers. Mock Cloudinary and Stripe in tests. +- **Frontend (M3/M4):** React Testing Library / Vitest for critical components — + `ProductCard`, `CartItem`, `OrderStatusTracker`, `RevenueChart`, `CouponInput`, + and at least 5 components total. +- **API (M4):** Postman collection lives in `postman/`, one folder per domain + (Auth, Products, Cart, Orders, Payment, Coupons, Reviews, Analytics), with + `{{baseUrl}}`, `{{authToken}}`, `{{adminToken}}` env variables and pre-request + scripts to auto-set tokens from login responses. Run via Newman: + ```bash + newman run postman/GeetaUniversity_MerchStore.postman_collection.json -e env.json + ``` + +Run lint and tests before opening any PR: +```bash +npm run lint +npm test +``` + +--- + +## Deployment + +- **Database:** MongoDB Atlas — confirm indexes (`User.email`, `Product.category`, + `Order.userId`, `Review.productId`), whitelist `0.0.0.0/0` for Render, use a + scoped `prod` DB user. +- **Backend:** Render — Web Service connected to `main`, env vars set in Render + dashboard, build `npm install`, start `node server.js`, verify `/api/health`. +- **Frontend:** Vercel — root directory `client/`, env vars set + (`VITE_API_BASE_URL` pointing at the Render URL), verify `npm run build` locally + before deploying. +- **Stripe webhook:** configure `https:///api/payment/webhook` in the + Stripe Dashboard; verify with `stripe trigger` in production. +- **CORS:** update `CLIENT_URL` on Render to the Vercel production URL once known. + +--- + +## Code Style + +- ESLint + Prettier are enforced repo-wide — run `npm run lint -- --fix` before + pushing. Zero ESLint errors is a merge requirement. +- Backend request validation: Zod/Joi schemas live only in `validators/`. +- Keep controllers thin — business logic belongs in `services/` (M2's domain) + where applicable. +- API responses always use the shared envelope: `{ success, data, message, pagination }`. +- Follow the existing folder structure; don't introduce new top-level directories + without a quick team discussion first. +- Mobile-first Tailwind, WCAG 2.1 AA target on all customer-facing pages (M3). + +--- + +## Environment Variables Reference + +Both `server/.env.example` and `client/.env.example` are kept up to date as the +single source of truth. Whenever you add a new variable: +1. Add it to the relevant `.env.example` with a placeholder value +2. Announce it in the team channel so others can pull it into their local `.env` +3. Never commit real secrets, API keys, or credentials to the repo + +--- + +## Definition of Done (per feature branch) + +- [ ] All owned endpoints/pages implemented per the task checklist for your week +- [ ] Unit/integration tests written and passing +- [ ] Lint clean, Prettier formatted +- [ ] API contract rows updated to `Implemented` status +- [ ] Postman collection updated (backend) or manually tested against live API (frontend) +- [ ] No merge conflicts with `dev` +- [ ] PR approved by at least 1 reviewer before merge + +--- + +## Questions? + +Ping the team channel or tag the relevant owner (M1–M4) directly in your PR. +When in doubt, open a Draft PR early — feedback is cheaper before code is +finished than after. From b585210da7eb942d1c31706a4fb44b33c1537b88 Mon Sep 17 00:00:00 2001 From: Subham Nayak Date: Thu, 2 Jul 2026 12:32:36 +0530 Subject: [PATCH 2/4] Clean up README by removing unnecessary sections Removed detailed project description and features from README. --- README.md | 211 ------------------------------------------------------ 1 file changed, 211 deletions(-) diff --git a/README.md b/README.md index f1fdfc3..8b13789 100644 --- a/README.md +++ b/README.md @@ -1,212 +1 @@ -# 🎓 Geeta University MerchStore -> Official e-commerce platform for authentic Geeta University branded merchandise. - -[![Status](https://img.shields.io/badge/status-in--development-yellow)]() -[![License](https://img.shields.io/badge/license-Internal-blue)]() - ---- - -## 📖 Overview - -**Geeta University MerchStore** is a dedicated B2C e-commerce web application built exclusively for the Geeta University community. Students, faculty, and staff can browse, order, and pay for officially branded merchandise — notebooks, T-shirts, hoodies, caps, stationery, and collectibles — through a single authoritative platform, eliminating fake or low-quality items in circulation. - -An integrated **Admin Dashboard** allows the store manager to handle inventory, pricing, order fulfillment, and sales analytics. The platform supports both **online payments (Stripe)** and **Cash on Delivery (COD)** for on-campus orders. - -| | | -|---|---| -| **Client** | Geeta University Store Department | -| **Contact** | Store Manager / Dean of Student Affairs | -| **Project Type** | B2C E-Commerce Web Application | -| **Target Users** | Students, Faculty & Staff of Geeta University | -| **Timeline** | 8 Weeks | -| **Scope** | Academic / Internal Internship Project | - ---- - -## ✨ Key Features - -| Module | Description | Priority | -|---|---|---| -| 🔐 Auth | University email + Google OAuth login (JWT + bcrypt) | P0 | -| 🛍️ Product Catalog | Browse with filters (category, size, color) | P0 | -| 🖼️ Product Detail | Image gallery, size chart, stock status | P0 | -| 🛒 Cart & Checkout | Add/remove items, quantity update, address selection | P0 | -| 💳 Payments | Stripe online payment + Cash on Delivery | P0 | -| 📦 Order Tracking | Real-time status: Placed → Packed → Shipped → Delivered | P1 | -| 🧑‍💼 Admin Dashboard | Manage products, orders, and order status | P0 | -| 📊 Inventory Mgmt | Stock tracking with low-stock email alerts | P1 | -| ⭐ Reviews & Ratings | Verified-purchase product reviews | P2 | -| 🎟️ Coupon System | Event-based discount codes (e.g. `FEST20`) | P2 | -| 📈 Sales Reports | Weekly/monthly revenue analytics | P1 | -| 📱 Responsive UI | Full mobile & desktop support | P0 | - -> **Priority key:** P0 = Must Have · P1 = Should Have · P2 = Nice to Have - ---- - -## 🧱 Tech Stack -``` -**Frontend** — React.js (Vite), React Router v6, Redux Toolkit, Tailwind CSS, Axios -**Backend** — Node.js, Express.js, REST API architecture, Multer -**Database** — MongoDB Atlas, Mongoose ODM -**Auth** — JWT, bcrypt.js, Role-Based Access Control (RBAC) -**Integrations** — Stripe (payments), Cloudinary (image CDN) -**Dev Tools** — VS Code, Postman, Git/GitHub, ESLint, Prettier, dotenv -**Deployment** — Frontend: Vercel/Netlify · Backend: Render.com · DB: MongoDB Atlas -``` ---- - -## 🏗️ Architecture - -``` - ┌──────────────────┐ - │ React Client │ - │ (Vite + Redux) │ - └─────────┬─────────┘ - │ Axios (REST) - ┌─────────▼─────────┐ - │ Express.js API │ - │ (JWT + RBAC) │ - └───┬───────────┬───┘ - │ │ - ┌─────────▼───┐ ┌───▼────────┐ - │ MongoDB Atlas│ │ Stripe API │ - │ (Mongoose) │ │ Cloudinary │ - └──────────────┘ └────────────┘ -``` - ---- - -## 🗂️ Database Schema (Collections) - -| Collection | Key Fields | -|---|---| -| **Users** | `name`, `email`, `password`, `role`, `addresses[]` | -| **Products** | `name`, `description`, `category`, `price`, `images[]`, `sizes[]`, `stock`, `ratings[]` | -| **Orders** | `userId`, `items[]`, `totalAmount`, `paymentMethod`, `status`, `address` | -| **Cart** | `userId`, `items[{productId, qty, size}]` | -| **Coupons** | `code`, `discountPct`, `expiresAt`, `usageLimit` | -| **Reviews** | `userId`, `productId`, `rating`, `comment`, `createdAt` | - ---- - -## 🔌 API Reference - -| Method | Endpoint | Description | -|---|---|---| -| `POST` | `/api/auth/register` | Register a new user | -| `POST` | `/api/auth/login` | Authenticate user, issue JWT | -| `GET` | `/api/products` | List products with filters | -| `GET` | `/api/products/:id` | Get single product details | -| `POST` | `/api/cart/add` | Add item to cart | -| `PUT` | `/api/cart/update` | Update cart item quantity | -| `POST` | `/api/orders/create` | Place a new order | -| `GET` | `/api/orders/:userId` | Get order history for a user | -| `PUT` | `/api/admin/orders/:id/status` | Admin: update order status | -| `GET` | `/api/admin/analytics/revenue` | Admin: revenue analytics | -| `POST` | `/api/coupons/validate` | Validate a discount coupon | -| `POST` | `/api/reviews/:productId` | Submit a product review | - -> Full request/response schemas available in the [Postman Collection](#) *(link to be added)*. - ---- - -## 🖥️ Screens - -- **Home / Landing** — Hero banner, featured products, category grid, announcements -- **Product Listing** — Filter sidebar, product grid, pagination, sort by price/rating -- **Product Detail** — Image slider, size selector, add-to-cart, related products -- **Cart** — Item list, quantity stepper, price breakdown -- **Checkout** — Address form, payment method, order summary -- **Order Confirmation** — Success state, order ID, estimated delivery -- **User Dashboard** — Profile, order history, saved addresses -- **Admin: Products** — Data table with add/edit/delete modal -- **Admin: Orders** — Order table with status update & filters -- **Admin: Analytics** — Revenue charts, top products, order count cards - ---- - -## 🚀 Getting Started - -### Prerequisites -- Node.js ≥ 18.x -- MongoDB Atlas account -- Stripe account (test mode keys) -- Cloudinary account - -### Installation - -```bash -# Clone the repository -git clone https://github.com//geeta-university-merchstore.git -cd geeta-university-merchstore - -# Install backend dependencies -cd server -npm install - -# Install frontend dependencies -cd ../client -npm install -``` - -### Environment Variables - -Create a `.env` file in `/server`: - -```env -PORT=5000 -MONGODB_URI=your_mongodb_atlas_uri -JWT_SECRET=your_jwt_secret -STRIPE_SECRET_KEY=your_stripe_secret_key -CLOUDINARY_CLOUD_NAME=your_cloud_name -CLOUDINARY_API_KEY=your_api_key -CLOUDINARY_API_SECRET=your_api_secret -GOOGLE_CLIENT_ID=your_google_oauth_client_id -``` - -### Run Locally - -```bash -# Backend -cd server -npm run dev - -# Frontend (in a new terminal) -cd client -npm run dev -``` - -App will be available at `http://localhost:5173` (frontend) and `http://localhost:5000` (API). - ---- - - - -## 🔒 Security & Non-Functional Requirements - -- **Security** — Stateless JWT auth, bcrypt hashing, HTTPS only, input validation (Joi/Zod), API rate limiting -- **Performance** — API response time < 500ms (95th percentile), DB indexing, lazy loading -- **Scalability** — Stateless backend (horizontally scalable), MongoDB Atlas managed scaling -- **Responsiveness** — Mobile-first (320px–1920px), tested on Chrome, Firefox, Safari -- **Accessibility** — WCAG 2.1 AA compliant, semantic HTML, keyboard navigation, ARIA labels -- **Code Quality** — ESLint + Prettier, Git branching (`main`/`dev`/`feature/*`), PR-based reviews -- **Testing** — Jest unit tests, manual E2E checklist, automated Postman collection - ---- - -## 🤝 Contributing - -1. Fork the repo and create your branch from `dev` (`feature/`) -2. Commit changes with clear messages -3. Open a Pull Request against `dev` for review -4. Ensure ESLint/Prettier checks pass before requesting review - ---- - -## 📄 License - -Internal academic project — Geeta University. Not for external commercial distribution. - ---- From 6c22fbeb33a9ea4775571afcf847a6e4daaf5849 Mon Sep 17 00:00:00 2001 From: Subham Nayak Date: Thu, 2 Jul 2026 12:33:00 +0530 Subject: [PATCH 3/4] Revise CONTRIBUTING.md to remove old content Removed outdated contributing guidelines and team structure details. --- CONTRIBUTING.md | 230 ------------------------------------------------ 1 file changed, 230 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e3d3117..8b13789 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,231 +1 @@ -# Contributing to Geeta University MerchStore -Welcome! This doc explains how our 4-member team works together on this repo — -ownership, branching, commit/PR conventions, the API contract process, weekly -merge schedule, setup, testing, and deployment. Read this fully before opening -your first PR. - ---- - -## Team & Ownership - -| Member | Role | Owns | -|--------|------|------| -| **M1** | Backend Lead — Auth, Models, Products | `server/config/`, `server/models/` (all 6: User, Product, Order, Cart, Coupon, Review), `server/middleware/`, `server/validators/authValidator.js`, `server/validators/productValidator.js`, `controllers/authController.js`, `controllers/productController.js`, `routes/authRoutes.js`, `routes/productRoutes.js`, `utils/generateToken.js`, `utils/cloudinaryUpload.js`, `server.js`, `.env.example` | -| **M2** | Backend — Transactions, Payments & Real-Time | `controllers/{cart,order,payment,coupon,review,analytics}Controller.js`, `routes/{cart,order,payment,coupon,review,analytics}Routes.js`, `validators/{cart,order,review}Validator.js`, `services/{stripe,email,inventory}Service.js`, `cron/lowStockCron.js`, `socket/orderSocket.js`, `utils/analyticsHelpers.js` | -| **M3** | Frontend — Customer UI & State | `client/src/app/store.js`, `features/{auth,cart,products,orders}/`, customer `pages/` (Home, Login, Register, ProductList, ProductDetail, Cart, Checkout, OrderConfirm, UserDashboard), shared `components/`, `hooks/`, `utils/api.js`, `App.jsx`, `main.jsx`, Tailwind config | -| **M4** | Frontend Admin + DevOps | `client/src/pages/admin/`, `components/admin/`, `components/{ReviewList,ReviewForm,CouponInput}.jsx`, `features/admin/`, `routes/AdminRoutes.jsx`, `postman/`, `docs/`, `.github/workflows/`, `.github/pull_request_template.md` | - -**Golden rule:** only edit files inside your owned folders. If you need a change in -someone else's file (e.g. a schema tweak in M1's model, or adding a section to M3's -`ProductDetail.jsx`), open a PR against *their* file, keep the diff minimal and -clearly marked, and request their review — never push directly to it. - ---- - -## Branching Strategy - -``` -main ← production-ready, tagged releases only -└── dev ← integration branch, always runnable - ├── feature/m1-auth-products - ├── feature/m2-transactions - ├── feature/m3-frontend - └── feature/m4-admin-integration -``` - -- **Sub-feature branches** are encouraged inside your own scope, e.g. - `feature/m1-models`, `feature/m1-auth`, `feature/m1-cloudinary`, - `feature/m2-stripe`, `feature/m2-sockets`. Merge these into your own - `feature/mX-*` branch first, then that branch merges into `dev`. -- **`main` branch protection:** requires 2 PR approvals + CI (ESLint + Jest) pass. -- **`dev` branch protection:** requires 1 PR approval + CI pass. -- **Merge strategy:** squash-merge `feature/*` → `dev`; regular merge commit - `dev` → `main`. - ---- - -## Commit & PR Conventions - -**Commit / PR title format:** -``` -[M] : -``` -Types: `feat`, `fix`, `chore`, `test`, `docs`, `refactor` - -Examples: -- `[M1] feat: add Product model with size/stock schema` -- `[M2] fix: prevent double stock decrement on Stripe retry` -- `[M3] feat: add ProductList filter sidebar` -- `[M4] feat: add RevenueChart to AdminAnalytics` - -**Every PR must include (see `.github/pull_request_template.md`):** -- [ ] What changed and why -- [ ] Linked API contract row(s), if applicable -- [ ] Screenshots/GIF for UI changes -- [ ] `npm run lint` passes with 0 errors, Prettier formatted -- [ ] Tests added/updated, `npm test` passes -- [ ] No edits outside your owned folders (or explicit sign-off from the file's owner) -- [ ] `.env.example` updated if new env vars were introduced - ---- - -## API Contract Process - -Before any backend endpoint is consumed by the frontend, it must be documented and -agreed in the shared **MerchStore API Contract** doc (Notion/Google Sheet): - -`Endpoint | Method | Request Body (JSON) | Response Shape (JSON) | Status Codes | Owner (M1/M2) | Consumer (M3/M4) | Status (Draft/Agreed/Implemented)` - -**Mandatory checkpoints:** - -1. **Week 1 — Full Draft Contract** - - M1 documents: Auth endpoints, Product endpoints (request/response shapes, error formats) - - M2 documents: Cart, Order, Payment, Coupon, Review, Analytics endpoints - - M3/M4 review and flag any missing fields needed by UI - - All 4 sign off — no changes to agreed fields without a PR discussion - -2. **Week 3 — Auth + Product Contract Locked** - - M1 deploys auth routes to `dev`; M3 tests against live API - - Field mismatch → raise a GitHub Issue, M1 fixes within 24hrs - - Response envelope `{ success, data, message, pagination }` strictly enforced - -3. **Week 4 — Cart + Order Contract Locked** - - M2 deploys cart/order routes; M3 tests Cart page and Checkout flow - - Stripe `clientSecret` field name confirmed (`data.clientSecret`) - - Socket event names confirmed: `ORDER_STATUS_UPDATED`, `PAYMENT_CONFIRMED` - -4. **Week 5 — Admin + Analytics Contract Locked** - - M2 deploys analytics routes; M4 connects charts - - M1 confirms admin product endpoints; M4 connects product form modal - - Review endpoint fields confirmed: `rating` (1–5 int), `comment` (string) - -**Breaking Change Policy** — any breaking change to an agreed endpoint requires: -1. A GitHub Issue labeled `breaking-change` -2. 24-hour notice in the team channel -3. PR review by the consuming member (M3 or M4) before merge -4. A version bump on the route (e.g. `/api/v2/...`) if the change cannot be - made backward-compatible - ---- - -## Weekly Checkpoint Merge Schedule - -| Week | Event | Branches Merging | -|------|-------|-------------------| -| 1 end | API contract document finalized & signed off by all 4 | No code merge yet | -| 2 end | M1 models + auth scaffolding ready | M1 merges `feature/m1-models` sub-branch → `dev` | -| 3 end | M1 Auth APIs + M3 React scaffold + Redux ready | M1 auth routes → `dev`; M3 scaffold → `dev` | -| 4 end | M1 Product APIs + M2 Cart APIs + M3 Product pages ready | All three → `dev` (coordinate sequentially, 1hr window) | -| 5 end | M2 Orders/Stripe/WebSocket + M3 Checkout + M4 Admin Products | Merge M2, M3 checkout, M4 admin → `dev` | -| 6 end | M2 Analytics/Coupons/Reviews + M4 Admin Orders/Analytics | Merge M2 and M4 remaining features → `dev` | -| 7 end | Deployment complete; all features on `dev` | `dev` → `main` (pre-release merge) | -| 8 end | Final QA, README, tag release | Tag `v1.0.0` on `main` | - ---- - -## Local Setup - -```bash -git clone -cd merchstore - -# backend -cd server -cp .env.example .env # fill in your own keys (Mongo, JWT, Google OAuth, Cloudinary, Stripe, SMTP) -npm install -npm run dev - -# frontend -cd ../client -cp .env.example .env # VITE_API_BASE_URL, VITE_STRIPE_PUBLISHABLE_KEY, VITE_SOCKET_URL, VITE_GOOGLE_CLIENT_ID -npm install -npm run dev -``` - -Backend runs on `http://localhost:5000`, frontend on `http://localhost:5173` -(Vite proxies `/api` to the backend — see `vite.config.js`). Never commit real -`.env` files — only `.env.example` is tracked in git. - ---- - -## Testing - -- **Backend (M1/M2):** Jest + Supertest. Target ≥70% coverage on auth, model - validation, order, and payment controllers. Mock Cloudinary and Stripe in tests. -- **Frontend (M3/M4):** React Testing Library / Vitest for critical components — - `ProductCard`, `CartItem`, `OrderStatusTracker`, `RevenueChart`, `CouponInput`, - and at least 5 components total. -- **API (M4):** Postman collection lives in `postman/`, one folder per domain - (Auth, Products, Cart, Orders, Payment, Coupons, Reviews, Analytics), with - `{{baseUrl}}`, `{{authToken}}`, `{{adminToken}}` env variables and pre-request - scripts to auto-set tokens from login responses. Run via Newman: - ```bash - newman run postman/GeetaUniversity_MerchStore.postman_collection.json -e env.json - ``` - -Run lint and tests before opening any PR: -```bash -npm run lint -npm test -``` - ---- - -## Deployment - -- **Database:** MongoDB Atlas — confirm indexes (`User.email`, `Product.category`, - `Order.userId`, `Review.productId`), whitelist `0.0.0.0/0` for Render, use a - scoped `prod` DB user. -- **Backend:** Render — Web Service connected to `main`, env vars set in Render - dashboard, build `npm install`, start `node server.js`, verify `/api/health`. -- **Frontend:** Vercel — root directory `client/`, env vars set - (`VITE_API_BASE_URL` pointing at the Render URL), verify `npm run build` locally - before deploying. -- **Stripe webhook:** configure `https:///api/payment/webhook` in the - Stripe Dashboard; verify with `stripe trigger` in production. -- **CORS:** update `CLIENT_URL` on Render to the Vercel production URL once known. - ---- - -## Code Style - -- ESLint + Prettier are enforced repo-wide — run `npm run lint -- --fix` before - pushing. Zero ESLint errors is a merge requirement. -- Backend request validation: Zod/Joi schemas live only in `validators/`. -- Keep controllers thin — business logic belongs in `services/` (M2's domain) - where applicable. -- API responses always use the shared envelope: `{ success, data, message, pagination }`. -- Follow the existing folder structure; don't introduce new top-level directories - without a quick team discussion first. -- Mobile-first Tailwind, WCAG 2.1 AA target on all customer-facing pages (M3). - ---- - -## Environment Variables Reference - -Both `server/.env.example` and `client/.env.example` are kept up to date as the -single source of truth. Whenever you add a new variable: -1. Add it to the relevant `.env.example` with a placeholder value -2. Announce it in the team channel so others can pull it into their local `.env` -3. Never commit real secrets, API keys, or credentials to the repo - ---- - -## Definition of Done (per feature branch) - -- [ ] All owned endpoints/pages implemented per the task checklist for your week -- [ ] Unit/integration tests written and passing -- [ ] Lint clean, Prettier formatted -- [ ] API contract rows updated to `Implemented` status -- [ ] Postman collection updated (backend) or manually tested against live API (frontend) -- [ ] No merge conflicts with `dev` -- [ ] PR approved by at least 1 reviewer before merge - ---- - -## Questions? - -Ping the team channel or tag the relevant owner (M1–M4) directly in your PR. -When in doubt, open a Draft PR early — feedback is cheaper before code is -finished than after. From ed157777f2093c9926871dcf10544d609e08693d Mon Sep 17 00:00:00 2001 From: Subham Nayak Date: Thu, 2 Jul 2026 17:35:30 +0530 Subject: [PATCH 4/4] Add Google OAuth authentication and update frontend --- client/package-lock.json | 40 +- client/src/App.jsx | 2 + client/src/components/FilterSidebar.jsx | 3 +- client/src/features/auth/authSlice.js | 48 +- client/src/features/cart/cartSlice.js | 3 +- client/src/features/orders/orderSlice.js | 29 +- client/src/features/products/productSlice.js | 28 +- client/src/pages/Cart.jsx | 31 +- client/src/pages/Checkout.jsx | 42 +- client/src/pages/Home.jsx | 3 +- client/src/pages/Login.jsx | 294 +- client/src/pages/OAuthSuccess.jsx | 43 + client/src/pages/OrderConfirm.jsx | 7 +- client/src/pages/ProductList.jsx | 7 +- client/src/pages/Register.jsx | 366 +- client/src/utils/api.js | 4 +- server/.env.example | 43 + server/config/cloudinary.js | 13 + server/config/db.js | 36 + server/config/passport.js | 68 + server/controllers/analyticsController.js | 200 + server/controllers/authController.js | 197 + server/controllers/cartController.js | 239 + server/controllers/couponController.js | 153 + server/controllers/orderController.js | 430 + server/controllers/paymentController.js | 134 + server/controllers/productController.js | 276 + server/controllers/reviewController.js | 202 + server/coverage/clover.xml | 554 + server/coverage/coverage-final.json | 10 + server/coverage/lcov-report/base.css | 224 + .../coverage/lcov-report/block-navigation.js | 87 + .../controllers/analyticsController.js.html | 685 + .../controllers/authController.js.html | 502 + .../controllers/cartController.js.html | 802 ++ .../controllers/couponController.js.html | 544 + .../lcov-report/controllers/index.html | 191 + .../controllers/orderController.js.html | 1216 ++ .../controllers/paymentController.js.html | 487 + .../controllers/productController.js.html | 913 ++ .../controllers/reviewController.js.html | 691 + server/coverage/lcov-report/favicon.png | Bin 0 -> 445 bytes server/coverage/lcov-report/index.html | 146 + server/coverage/lcov-report/prettify.css | 1 + server/coverage/lcov-report/prettify.js | 2 + .../lcov-report/services/emailService.js.html | 679 + .../coverage/lcov-report/services/index.html | 116 + .../services/inventoryService.js.html | 520 + .../services/stripeService.js.html | 406 + .../lcov-report/sort-arrow-sprite.png | Bin 0 -> 138 bytes server/coverage/lcov-report/sorter.js | 210 + .../utils/analyticsHelpers.js.html | 610 + .../utils/cloudinaryUpload.js.html | 310 + .../lcov-report/utils/generateToken.js.html | 157 + server/coverage/lcov-report/utils/index.html | 131 + server/coverage/lcov.info | 1022 ++ server/cron/lowStockCron.js | 76 + server/middleware/authMiddleware.js | 75 + server/middleware/errorHandler.js | 65 + server/middleware/rateLimiter.js | 58 + server/middleware/validate.js | 33 + server/models/Cart.js | 33 + server/models/Coupon.js | 28 + server/models/Order.js | 61 + server/models/Product.js | 40 + server/models/Review.js | 29 + server/models/User.js | 47 + server/package-lock.json | 11335 ++++++++++++++++ server/package.json | 68 + server/routes/analyticsRoutes.js | 21 + server/routes/authRoutes.js | 39 + server/routes/cartRoutes.js | 28 + server/routes/couponRoutes.js | 21 + server/routes/orderRoutes.js | 52 + server/routes/paymentRoutes.js | 16 + server/routes/productRoutes.js | 47 + server/routes/reviewRoutes.js | 28 + server/seed.js | 329 + server/server.js | 141 + server/services/emailService.js | 198 + server/services/inventoryService.js | 145 + server/services/stripeService.js | 121 + server/socket/orderSocket.js | 155 + server/tests/auth.test.js | 212 + server/tests/cart.test.js | 238 + server/tests/coupon.test.js | 132 + server/tests/inventoryService.test.js | 123 + server/tests/order.test.js | 442 + server/tests/payment.test.js | 185 + server/tests/product.test.js | 334 + server/tests/utils.test.js | 97 + server/utils/analyticsHelpers.js | 175 + server/utils/cloudinaryUpload.js | 75 + server/utils/generateToken.js | 24 + server/validators/authValidator.js | 31 + server/validators/cartValidator.js | 41 + server/validators/orderValidator.js | 61 + server/validators/productValidator.js | 66 + server/validators/reviewValidator.js | 24 + 99 files changed, 29430 insertions(+), 276 deletions(-) create mode 100644 client/src/pages/OAuthSuccess.jsx create mode 100644 server/.env.example create mode 100644 server/config/cloudinary.js create mode 100644 server/config/db.js create mode 100644 server/config/passport.js create mode 100644 server/controllers/analyticsController.js create mode 100644 server/controllers/authController.js create mode 100644 server/controllers/cartController.js create mode 100644 server/controllers/couponController.js create mode 100644 server/controllers/orderController.js create mode 100644 server/controllers/paymentController.js create mode 100644 server/controllers/productController.js create mode 100644 server/controllers/reviewController.js create mode 100644 server/coverage/clover.xml create mode 100644 server/coverage/coverage-final.json create mode 100644 server/coverage/lcov-report/base.css create mode 100644 server/coverage/lcov-report/block-navigation.js create mode 100644 server/coverage/lcov-report/controllers/analyticsController.js.html create mode 100644 server/coverage/lcov-report/controllers/authController.js.html create mode 100644 server/coverage/lcov-report/controllers/cartController.js.html create mode 100644 server/coverage/lcov-report/controllers/couponController.js.html create mode 100644 server/coverage/lcov-report/controllers/index.html create mode 100644 server/coverage/lcov-report/controllers/orderController.js.html create mode 100644 server/coverage/lcov-report/controllers/paymentController.js.html create mode 100644 server/coverage/lcov-report/controllers/productController.js.html create mode 100644 server/coverage/lcov-report/controllers/reviewController.js.html create mode 100644 server/coverage/lcov-report/favicon.png create mode 100644 server/coverage/lcov-report/index.html create mode 100644 server/coverage/lcov-report/prettify.css create mode 100644 server/coverage/lcov-report/prettify.js create mode 100644 server/coverage/lcov-report/services/emailService.js.html create mode 100644 server/coverage/lcov-report/services/index.html create mode 100644 server/coverage/lcov-report/services/inventoryService.js.html create mode 100644 server/coverage/lcov-report/services/stripeService.js.html create mode 100644 server/coverage/lcov-report/sort-arrow-sprite.png create mode 100644 server/coverage/lcov-report/sorter.js create mode 100644 server/coverage/lcov-report/utils/analyticsHelpers.js.html create mode 100644 server/coverage/lcov-report/utils/cloudinaryUpload.js.html create mode 100644 server/coverage/lcov-report/utils/generateToken.js.html create mode 100644 server/coverage/lcov-report/utils/index.html create mode 100644 server/coverage/lcov.info create mode 100644 server/cron/lowStockCron.js create mode 100644 server/middleware/authMiddleware.js create mode 100644 server/middleware/errorHandler.js create mode 100644 server/middleware/rateLimiter.js create mode 100644 server/middleware/validate.js create mode 100644 server/models/Cart.js create mode 100644 server/models/Coupon.js create mode 100644 server/models/Order.js create mode 100644 server/models/Product.js create mode 100644 server/models/Review.js create mode 100644 server/models/User.js create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/routes/analyticsRoutes.js create mode 100644 server/routes/authRoutes.js create mode 100644 server/routes/cartRoutes.js create mode 100644 server/routes/couponRoutes.js create mode 100644 server/routes/orderRoutes.js create mode 100644 server/routes/paymentRoutes.js create mode 100644 server/routes/productRoutes.js create mode 100644 server/routes/reviewRoutes.js create mode 100644 server/seed.js create mode 100644 server/server.js create mode 100644 server/services/emailService.js create mode 100644 server/services/inventoryService.js create mode 100644 server/services/stripeService.js create mode 100644 server/socket/orderSocket.js create mode 100644 server/tests/auth.test.js create mode 100644 server/tests/cart.test.js create mode 100644 server/tests/coupon.test.js create mode 100644 server/tests/inventoryService.test.js create mode 100644 server/tests/order.test.js create mode 100644 server/tests/payment.test.js create mode 100644 server/tests/product.test.js create mode 100644 server/tests/utils.test.js create mode 100644 server/utils/analyticsHelpers.js create mode 100644 server/utils/cloudinaryUpload.js create mode 100644 server/utils/generateToken.js create mode 100644 server/validators/authValidator.js create mode 100644 server/validators/cartValidator.js create mode 100644 server/validators/orderValidator.js create mode 100644 server/validators/productValidator.js create mode 100644 server/validators/reviewValidator.js diff --git a/client/package-lock.json b/client/package-lock.json index edf8f14..daa3b11 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -50,6 +50,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -829,7 +852,6 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.9.0.tgz", "integrity": "sha512-Vwqe6Q5cU4i82tPyAv2BpaW/fQSNdOSO4/J8EeDLPp5/oIZiMmdB+Hgh863zFH+rtoxpuWGvD1L7QPh8k1Rdvw==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.16" } @@ -851,7 +873,6 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1065,7 +1086,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", @@ -1214,8 +1234,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", @@ -1738,7 +1757,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2322,7 +2340,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -2518,7 +2535,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2528,7 +2544,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2541,7 +2556,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.80.0.tgz", "integrity": "sha512-4P+fk6oXsxY+6xSj7Euhc2sumQD8zQqCuVHoJwoyp9EchP+IUW9OESB7uHFJOKsIBQ4MQqYE84INJFqUCYNoOg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -2590,7 +2604,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -2687,8 +2700,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -3058,7 +3070,6 @@ "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3166,7 +3177,6 @@ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/client/src/App.jsx b/client/src/App.jsx index c8ee493..785ebfc 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -18,6 +18,7 @@ import Cart from './pages/Cart'; import Checkout from './pages/Checkout'; import OrderConfirm from './pages/OrderConfirm'; import UserDashboard from './pages/UserDashboard'; +import OAuthSuccess from './pages/OAuthSuccess'; // State Actions import { fetchCurrentUser } from './features/auth/authSlice'; @@ -69,6 +70,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/client/src/components/FilterSidebar.jsx b/client/src/components/FilterSidebar.jsx index 8f0ce0b..8548c79 100644 --- a/client/src/components/FilterSidebar.jsx +++ b/client/src/components/FilterSidebar.jsx @@ -7,7 +7,8 @@ const FilterSidebar = ({ filters, onFilterChange, onReset, onClose }) => { { name: 'Varsity Hoodies', value: 'hoodies' }, { name: 'Heritage Tees', value: 'tshirts' }, { name: 'Crewneck Sweatshirts', value: 'sweatshirts' }, - { name: 'Academic Accessories', value: 'accessories' } + { name: 'Academic Accessories', value: 'accessories' }, + { name: 'University Stationery', value: 'stationery' } ]; const sizes = ['S', 'M', 'L', 'XL', 'One Size', 'Standard']; diff --git a/client/src/features/auth/authSlice.js b/client/src/features/auth/authSlice.js index a20c871..417270c 100644 --- a/client/src/features/auth/authSlice.js +++ b/client/src/features/auth/authSlice.js @@ -1,6 +1,31 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import api from '../../utils/api'; +const normalizeAuthPayload = (payload) => { + if (!payload || typeof payload !== 'object') { + return { token: null, user: null }; + } + + const source = payload.data && typeof payload.data === 'object' ? payload.data : payload; + + return { + token: source.token || null, + user: source.user || null, + }; +}; + +const normalizeUserPayload = (payload) => { + if (!payload || typeof payload !== 'object') { + return null; + } + + if (payload.data && typeof payload.data === 'object') { + return payload.data; + } + + return payload.user || null; +}; + // Async Thunks export const registerUser = createAsyncThunk( 'auth/register', @@ -86,11 +111,14 @@ const authSlice = createSlice({ state.error = null; }) .addCase(registerUser.fulfilled, (state, action) => { + const { token, user } = normalizeAuthPayload(action.payload); state.loading = false; - state.token = action.payload.token; - state.user = action.payload.user; + state.token = token; + state.user = user; state.success = true; - localStorage.setItem('token', action.payload.token); + if (token) { + localStorage.setItem('token', token); + } }) .addCase(registerUser.rejected, (state, action) => { state.loading = false; @@ -103,11 +131,14 @@ const authSlice = createSlice({ state.error = null; }) .addCase(loginUser.fulfilled, (state, action) => { + const { token, user } = normalizeAuthPayload(action.payload); state.loading = false; - state.token = action.payload.token; - state.user = action.payload.user; + state.token = token; + state.user = user; state.success = true; - localStorage.setItem('token', action.payload.token); + if (token) { + localStorage.setItem('token', token); + } }) .addCase(loginUser.rejected, (state, action) => { state.loading = false; @@ -120,7 +151,7 @@ const authSlice = createSlice({ }) .addCase(fetchCurrentUser.fulfilled, (state, action) => { state.loading = false; - state.user = action.payload.user; + state.user = normalizeUserPayload(action.payload); }) .addCase(fetchCurrentUser.rejected, (state) => { state.loading = false; @@ -132,7 +163,8 @@ const authSlice = createSlice({ // Add Address .addCase(addAddress.fulfilled, (state, action) => { if (state.user) { - state.user.addresses = action.payload.addresses; + // Backend returns { success, data: { addresses } } + state.user.addresses = action.payload.addresses || action.payload.data?.addresses || []; } }); }, diff --git a/client/src/features/cart/cartSlice.js b/client/src/features/cart/cartSlice.js index 2845e58..1ff87bc 100644 --- a/client/src/features/cart/cartSlice.js +++ b/client/src/features/cart/cartSlice.js @@ -87,7 +87,8 @@ const cartSlice = createSlice({ }) .addCase(fetchCart.fulfilled, (state, action) => { state.loading = false; - state.items = action.payload.cart; + // Backend returns { success, data: { items, itemCount, subtotal } } + state.items = action.payload?.data?.items ?? action.payload?.cart ?? []; }) .addCase(fetchCart.rejected, (state, action) => { state.loading = false; diff --git a/client/src/features/orders/orderSlice.js b/client/src/features/orders/orderSlice.js index a87f955..0c10f20 100644 --- a/client/src/features/orders/orderSlice.js +++ b/client/src/features/orders/orderSlice.js @@ -11,7 +11,9 @@ export const createOrder = createAsyncThunk( dispatch(clearCartLocal()); // Clear local Redux cart return response.data; } catch (error) { - return rejectWithValue(error.response?.data?.message || 'Failed to place order'); + const errData = error.response?.data; + const validationMsg = errData?.errors?.[0]?.message; + return rejectWithValue(validationMsg || errData?.message || 'Failed to place order'); } } ); @@ -21,7 +23,9 @@ export const validateCoupon = createAsyncThunk( async (code, { rejectWithValue }) => { try { const response = await api.post('/coupons/validate', { code }); - return { code, discountPct: response.data.discountPct }; + // Backend returns { success, data: { code, discountPct, discountAmount, ... } } + const payload = response.data?.data || response.data; + return { code, discountPct: payload.discountPct }; } catch (error) { return rejectWithValue(error.response?.data?.message || 'Invalid coupon code'); } @@ -30,9 +34,14 @@ export const validateCoupon = createAsyncThunk( export const fetchUserOrders = createAsyncThunk( 'orders/fetchAll', - async (_, { rejectWithValue }) => { + async (_, { rejectWithValue, getState }) => { try { - const response = await api.get('/orders'); + const userId = getState().auth.user?._id; + if (!userId) { + return rejectWithValue('Not authenticated'); + } + + const response = await api.get(`/orders/${userId}`); return response.data; } catch (error) { return rejectWithValue(error.response?.data?.message || 'Failed to load order history'); @@ -86,9 +95,10 @@ const orderSlice = createSlice({ }) .addCase(createOrder.fulfilled, (state, action) => { state.loading = false; - state.currentOrder = action.payload.order; + const data = action.payload?.data; + state.currentOrder = data?.order ?? (data?._id ? data : null) ?? action.payload?.order ?? null; state.success = true; - state.activeCoupon = null; // Reset coupon after purchase + state.activeCoupon = null; }) .addCase(createOrder.rejected, (state, action) => { state.loading = false; @@ -114,7 +124,9 @@ const orderSlice = createSlice({ }) .addCase(fetchUserOrders.fulfilled, (state, action) => { state.loading = false; - state.list = action.payload.orders; + // Backend returns { success, data: [...orders] } + const raw = action.payload?.data; + state.list = Array.isArray(raw) ? raw : (action.payload?.orders ?? []); }) .addCase(fetchUserOrders.rejected, (state, action) => { state.loading = false; @@ -127,7 +139,8 @@ const orderSlice = createSlice({ }) .addCase(fetchOrderById.fulfilled, (state, action) => { state.loading = false; - state.currentOrder = action.payload.order; + const data = action.payload?.data; + state.currentOrder = data?.order ?? (data?._id ? data : null) ?? action.payload?.order ?? null; }) .addCase(fetchOrderById.rejected, (state, action) => { state.loading = false; diff --git a/client/src/features/products/productSlice.js b/client/src/features/products/productSlice.js index 7f0ca41..7d8d52e 100644 --- a/client/src/features/products/productSlice.js +++ b/client/src/features/products/productSlice.js @@ -1,6 +1,30 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import api from '../../utils/api'; +const normalizeProductsPayload = (payload) => { + if (Array.isArray(payload)) return payload; + if (!payload || typeof payload !== 'object') return []; + + if (Array.isArray(payload.data)) return payload.data; + if (Array.isArray(payload.products)) return payload.products; + + return []; +}; + +const normalizeProductPayload = (payload) => { + if (!payload || typeof payload !== 'object') return null; + + if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) { + return payload.data; + } + + if (payload.product && typeof payload.product === 'object' && !Array.isArray(payload.product)) { + return payload.product; + } + + return null; +}; + // Async Thunks export const fetchProducts = createAsyncThunk( 'products/fetchAll', @@ -64,7 +88,7 @@ const productSlice = createSlice({ }) .addCase(fetchProducts.fulfilled, (state, action) => { state.loading = false; - state.items = action.payload.products; + state.items = normalizeProductsPayload(action.payload); }) .addCase(fetchProducts.rejected, (state, action) => { state.loading = false; @@ -78,7 +102,7 @@ const productSlice = createSlice({ }) .addCase(fetchProductById.fulfilled, (state, action) => { state.loading = false; - state.selectedProduct = action.payload.product; + state.selectedProduct = normalizeProductPayload(action.payload); }) .addCase(fetchProductById.rejected, (state, action) => { state.loading = false; diff --git a/client/src/pages/Cart.jsx b/client/src/pages/Cart.jsx index a3f0d36..73f9eb0 100644 --- a/client/src/pages/Cart.jsx +++ b/client/src/pages/Cart.jsx @@ -10,23 +10,22 @@ const Cart = () => { const dispatch = useDispatch(); const navigate = useNavigate(); const { items: cartItems, loading } = useSelector((state) => state.cart); + const safeCartItems = Array.isArray(cartItems) ? cartItems : []; useEffect(() => { dispatch(fetchCart()); }, [dispatch]); - // Calculations - const subtotal = cartItems.reduce((acc, item) => acc + (item.product?.price || 0) * item.qty, 0); - const totalItemsCount = cartItems.reduce((acc, item) => acc + item.qty, 0); - const estimatedDelivery = subtotal > 1000 ? 0 : 60; // Free delivery above 1000 INR + // Calculations - backend returns flat items: { productId, name, price, image, size, qty, stock } + const subtotal = safeCartItems.reduce((acc, item) => acc + (item.price || 0) * item.qty, 0); + const totalItemsCount = safeCartItems.reduce((acc, item) => acc + item.qty, 0); + const estimatedDelivery = subtotal > 1000 ? 0 : 60; const handleQtyChange = (item, newQty) => { if (newQty < 1) return; - // Check stock limit for that product size - const sizeObj = item.product?.sizes?.find(s => s.size === item.size); - if (sizeObj && newQty > sizeObj.stock) { - toast.error(`Sorry, only ${sizeObj.stock} units available in size ${item.size}.`); + if (item.stock !== undefined && newQty > item.stock) { + toast.error(`Sorry, only ${item.stock} units available in size ${item.size}.`); return; } @@ -53,13 +52,13 @@ const Cart = () => { return (
- {loading && cartItems.length === 0 && } + {loading && safeCartItems.length === 0 && }

Your Shopping Cart

- {cartItems.length === 0 ? ( + {safeCartItems.length === 0 ? ( /* Empty Cart State */
@@ -81,7 +80,7 @@ const Cart = () => {
{/* LEFT: CART ITEMS LIST (lg:col-span-8) */}
- {cartItems.map((item) => ( + {safeCartItems.map((item) => (
{ {/* Product Thumbnail */}
{item.product?.name}
@@ -98,13 +97,13 @@ const Cart = () => { {/* Info details */}

- {item.product?.name} + {item.name || item.product?.name}

Size: {item.size} - ₹{item.product?.price?.toLocaleString('en-IN')}.00 each + ₹{(item.price || item.product?.price || 0).toLocaleString('en-IN')}.00 each
@@ -132,7 +131,7 @@ const Cart = () => { {/* Total Item Price */} - ₹{((item.product?.price || 0) * item.qty).toLocaleString('en-IN')}.00 + ₹{((item.price || item.product?.price || 0) * item.qty).toLocaleString('en-IN')}.00 {/* Trash Bin button */} diff --git a/client/src/pages/Checkout.jsx b/client/src/pages/Checkout.jsx index 675aa61..07b1523 100644 --- a/client/src/pages/Checkout.jsx +++ b/client/src/pages/Checkout.jsx @@ -26,6 +26,7 @@ const Checkout = () => { const navigate = useNavigate(); const { items: cartItems, loading: cartLoading } = useSelector((state) => state.cart); + const safeCartItems = Array.isArray(cartItems) ? cartItems : []; const { user } = useSelector((state) => state.auth); const { activeCoupon, couponError, loading: orderLoading } = useSelector((state) => state.orders); @@ -66,14 +67,14 @@ const Checkout = () => { } }, [user]); - // Calculations - const subtotal = cartItems.reduce((acc, item) => acc + (item.product?.price || 0) * item.qty, 0); + // Calculations - backend returns flat items: { productId, name, price, image, size, qty } + const subtotal = safeCartItems.reduce((acc, item) => acc + (item.price || item.product?.price || 0) * item.qty, 0); const delivery = subtotal > 1000 ? 0 : 60; const discount = activeCoupon ? Math.round(subtotal * (activeCoupon.discountPct / 100)) : 0; const total = subtotal + delivery - discount; // Safeguard redirect if cart empty - if (!cartLoading && cartItems.length === 0) { + if (!cartLoading && safeCartItems.length === 0) { return (

Your Cart is Empty

@@ -129,26 +130,29 @@ const Checkout = () => { }; const handlePlaceOrder = () => { - // Get address text - let finalAddressText = ''; - if (selectedAddressId) { - const selected = user.addresses.find(a => a._id === selectedAddressId); - if (selected) { - finalAddressText = `${selected.street}, ${selected.city}, ${selected.state} - ${selected.pincode}`; - } - } - - if (!finalAddressText) { + if (!selectedAddressId || !user?.addresses?.length) { toast.error('Shipping address details missing.'); return; } const orderPayload = { - address: finalAddressText, + addressId: selectedAddressId, paymentMethod, couponCode: activeCoupon?.code || null, }; + const navigateToConfirmation = (res) => { + const orderId = + res?.data?.orderId || + res?.data?.order?._id || + res?.order?._id; + if (!orderId) { + toast.error('Order placed but confirmation page could not be loaded.'); + return; + } + navigate(`/order-confirm/${orderId}`); + }; + if (paymentMethod === 'stripe') { // Basic credit card validate mockup if (!cardNumber || !cardExpiry || !cardCvc) { @@ -164,7 +168,7 @@ const Checkout = () => { .then((res) => { setIsProcessingPayment(false); toast.success('Payment authorized! Order placed.'); - navigate(`/order-confirm/${res.order._id}`); + navigateToConfirmation(res); }) .catch((err) => { setIsProcessingPayment(false); @@ -177,7 +181,7 @@ const Checkout = () => { .unwrap() .then((res) => { toast.success('Order placed successfully (COD)'); - navigate(`/order-confirm/${res.order._id}`); + navigateToConfirmation(res); }) .catch((err) => { toast.error(err || 'Order processing failed'); @@ -567,14 +571,14 @@ const Checkout = () => {

Items List

- {cartItems.map((item, idx) => ( + {safeCartItems.map((item, idx) => (
- {item.product?.name} + {item.name || item.product?.name} ({item.size})
- {item.qty} × ₹{item.product?.price} + {item.qty} × ₹{item.price || item.product?.price}
))} diff --git a/client/src/pages/Home.jsx b/client/src/pages/Home.jsx index 3a51da7..65df86e 100644 --- a/client/src/pages/Home.jsx +++ b/client/src/pages/Home.jsx @@ -9,6 +9,7 @@ import Loader from '../components/Loader'; const Home = () => { const dispatch = useDispatch(); const { items: products, loading } = useSelector((state) => state.products); + const safeProducts = Array.isArray(products) ? products : []; useEffect(() => { // Fetch products on mount @@ -16,7 +17,7 @@ const Home = () => { }, [dispatch]); // Featured products - const featuredProducts = products.filter(p => p.isFeatured).slice(0, 4); + const featuredProducts = safeProducts.filter(p => p.isFeatured).slice(0, 4); const categories = [ { diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx index 342c310..e698ef2 100644 --- a/client/src/pages/Login.jsx +++ b/client/src/pages/Login.jsx @@ -1,161 +1,254 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; -import { Mail, Lock, ArrowRight } from 'lucide-react'; +import { Mail, Lock, Eye, EyeOff, ArrowRight, ShieldCheck } from 'lucide-react'; import toast from 'react-hot-toast'; import { loginUser, clearAuthError } from '../features/auth/authSlice'; import Loader from '../components/Loader'; -// Zod Schema +const GU_DOMAINS = ['@geeta.ac.in', '@geetauniversity.ac.in', '@geetauniversity.edu.in']; +const isGUEmail = (val) => GU_DOMAINS.some(d => val.toLowerCase().endsWith(d)); + const loginSchema = z.object({ - email: z.string().min(1, 'Email is required').email('Please enter a valid email address'), + email: z.string() + .min(1, 'Email is required') + .email('Please enter a valid email address') + .refine(isGUEmail, { message: 'Only Geeta University emails are allowed' }), password: z.string().min(6, 'Password must be at least 6 characters'), }); const Login = () => { const dispatch = useDispatch(); - const navigate = useNavigate(); - const location = useLocation(); - const { loading, error, token } = useSelector((state) => state.auth); - + const navigate = useNavigate(); + const location = useLocation(); + const { loading, error, token } = useSelector((s) => s.auth); const from = location.state?.from?.pathname || '/dashboard'; - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ + const [showPassword, setShowPassword] = useState(false); + const [emailFocused, setEmailFocused] = useState(false); + const [passFocused, setPassFocused] = useState(false); + + const { register, handleSubmit, watch, formState: { errors } } = useForm({ resolver: zodResolver(loginSchema), }); + const emailVal = watch('email', ''); + const emailValid = isGUEmail(emailVal) && emailVal.includes('@'); + useEffect(() => { - // If already logged in, redirect away - if (token) { - navigate(from, { replace: true }); - } - return () => { - dispatch(clearAuthError()); - }; + if (token) navigate(from, { replace: true }); + return () => { dispatch(clearAuthError()); }; }, [token, navigate, from, dispatch]); const onSubmit = (data) => { dispatch(loginUser(data)) .unwrap() - .then(() => { - toast.success('Welcome back to GU MerchStore!'); - }) - .catch((err) => { - toast.error(err || 'Failed to authenticate'); - }); + .then(() => toast.success('Welcome back to GU MerchStore! 🎓')) + .catch((err) => toast.error(err || 'Failed to authenticate')); + }; + + const handleGoogle = () => { + window.location.href = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api'}/auth/google`; }; return ( -
+
{loading && } - -
- {/* Card Header branding */} -
-
- GU + + {/* ── Animated Background Orbs ─────────────────────────── */} +
+
+
+
+ {/* Grid pattern */} +
+
+ + {/* ── Left Hero Panel ──────────────────────────────────── */} +
+ {/* University branding */} +
+
+ GU +
+
+

Geeta University

+

MerchStore Portal

+
+
+ + {/* Big headline */} +
+
+ + University Exclusive Access
-

- Sign In -

-

- Access your university account +

+ Welcome Back to{' '} + + GU Merch + +

+

+ Your one-stop campus store for official Geeta University merchandise, stationery and gear.

+ + {/* Stats row */} +
+ {[['500+', 'Students'], ['50+', 'Products'], ['4.9★', 'Rating']].map(([val, label]) => ( +
+

{val}

+

{label}

+
+ ))} +
- {/* Form Card */} -
+ {/* Bottom tag */} +

© 2026 Geeta University. All rights reserved.

+
+ + {/* ── Right Form Panel ─────────────────────────────────── */} +
+ {/* Glassmorphism card */} +
+ + {/* Mobile logo */} +
+
+ GU +
+ GU MerchStore +
+ +
+

Sign In

+

Use your official Geeta University email

+
+ + {/* Error Banner */} {error && ( -
- {error} +
+ + {error}
)} -
- {/* Email field */} + + {/* Email */}
-