A full-stack phone rental web application built with React 18 + TypeScript + Vite (frontend) and Express + SQLite (backend). Rent smartphones by the day with transparent MRP-based pricing, refundable deposits, and a membership programme.
Live Demo: After hosting, update this link.
/membership/admin-login)Rent-a-Phone/
βββ frontend/ # React 18 + TypeScript + Vite
β βββ public/
β β βββ assets/images/ # Fallback SVG
β βββ src/
β βββ api/index.ts # Axios client, types, API functions
β βββ components/
β β βββ Navbar.tsx # Nav bar with membership link
β β βββ PhoneCard.tsx # Phone card with OS, deposit, premium badge
β β βββ ProtectedRoute.tsx # Auth guard
β β βββ RentalModal.tsx # Date-picker rental modal
β βββ context/
β β βββ AuthContext.tsx # JWT auth + expiry check
β β βββ CartContext.tsx # Shopping cart state
β βββ data/
β β βββ catalog.ts # 30 phones: images, specs, pricing, tiers
β βββ pages/
β β βββ Home.tsx # Hero + marketing + membership promo
β β βββ Phones.tsx # Filterable phone grid
β β βββ PhoneDetail.tsx # Full specs, deposit, rent-to-own progress
β β βββ Cart.tsx # Cart with deposit totals
β β βββ Checkout.tsx # Razorpay-style UPI/Card/NetBanking
β β βββ Membership.tsx # Membership subscribe + status
β β βββ MyRentals.tsx # User's rental history
β β βββ Login.tsx # User login
β β βββ Register.tsx # User registration
β β βββ Admin.tsx # Phone catalog management
β β βββ AdminLogin.tsx # Admin authentication
β βββ utils/
β β βββ catalogStore.ts # localStorage catalog read/write
β β βββ pricing.ts # Discount & rent-to-own calculations
β βββ App.tsx # Routes
β βββ index.css # Full design system (~2000 lines)
βββ backend/
β βββ src/
β βββ db.ts # SQLite setup, migrations, 30-phone seed
β βββ index.ts # Express entry, CORS, routes
β βββ middleware/auth.ts # JWT verification middleware
β βββ routes/
β βββ phones.ts # GET /api/phones (with OS, premium_only)
β βββ auth.ts # POST login/register (with membership status)
β βββ rentals.ts # POST/GET rentals (deposit, premium check)
β βββ membership.ts # GET status, POST subscribe
βββ scripts/
β βββ update-phones.ts # CLI tool to update phone catalog
βββ .gitignore
βββ README.md
git clone https://github.com/EnsieT/rent-a-phone.git
cd rent-a-phone
cd backend
npm install
Create a .env file (or use the included .env.example):
JWT_SECRET=change-me-in-production
PORT=3001
Start the backend:
npx tsx src/index.ts
# or: npm run dev (uses ts-node-dev)
# Server runs at http://localhost:3001
The SQLite database is auto-created in backend/data/rental.db on first run with all 30 phones seeded.
cd frontend
npm install
npm run dev
# Runs at http://localhost:5173
Visit http://localhost:5173 β register an account and start browsing phones.
Admin panel: Go to /admin-login and log in with:
adminadmin123Each phone has an MRP (retail price). The buy price depends on condition:
| Condition | Factor | Example (MRP βΉ1,00,000) |
|---|---|---|
| Mint | 85% | βΉ85,000 |
| Like New | 80% | βΉ80,000 |
| Good | 65% | βΉ65,000 |
| Fair | 50% | βΉ50,000 |
Rent per day = Math.round(buyPrice / 400)
| Days Rented | Discount |
|---|---|
| 0β29 | 0% |
| 30β59 | 3% |
| 60β89 | 6% |
| 90β119 | 9% |
| β¦ | β¦ |
| 300+ | 30% (max) |
Formula: discount = floor(days / 30) Γ 3%, capped at 30%.
Every rental requires a refundable deposit = buy price. Members get βΉ9,000 off each deposit.
After 365 cumulative rental days on a specific phone, you become eligible for the rent-to-own programme.
| Feature | Detail |
|---|---|
| Price | βΉ10,000 / year |
| Deposit discount | βΉ9,000 off every rental deposit |
| Premium phone access | 4 exclusive flagship devices |
| Refund policy | 100% refundable |
| Priority support | β |
Premium-only phones: iPhone 15 Pro Max, iPhone 15 Pro, Galaxy S24 Ultra, Galaxy Z Fold 5.
catalog.tsThe frontend phone catalog lives in frontend/src/data/catalog.ts. Each phone entry looks like:
{
id: 31,
brand: 'Samsung',
model: 'Galaxy S25',
description: 'Latest Samsung flagship.',
tier: 'premium', // 'budget' | 'mid-tier' | 'premium'
imagePath: 'https://fdn2.gsmarena.com/vv/bigpic/samsung-galaxy-s25.jpg',
ram: '12 GB',
storage: '256 GB',
os: 'Android 15 (One UI 7)',
condition: 'Mint', // 'Mint' | 'Like New' | 'Good' | 'Fair'
mrp: 79999,
available: true,
premiumOnly: false, // true = members only
}
Buy price and rent/day are auto-calculated from MRP Γ condition factor.
scripts/update-phones.tsA guided script to add, edit, or remove phones from both catalog files at once:
npx tsx scripts/update-phones.ts
This will prompt you to:
Changes are written to both frontend/src/data/catalog.ts and backend/src/db.ts.
All images use GSMArena product photos. To find an image URL for a new phone:
https://fdn2.gsmarena.com/vv/bigpic/{phone-slug}.jpg| Tier | MRP Range | Examples |
|---|---|---|
| Budget | Under βΉ20,000 | Redmi Note 13 Pro, Galaxy M34, Poco X5 Pro |
| Mid-Tier | βΉ20,000 β βΉ50,000 | iPhone 14, Pixel 7a, Nothing Phone (2) |
| Premium | βΉ50,000+ | iPhone 15 Pro Max, Galaxy S24 Ultra, OnePlus 12 |
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| Styling | Plain CSS (custom design system) |
| Routing | React Router v6 |
| HTTP | Axios |
| Backend | Express.js, TypeScript |
| Database | SQLite via better-sqlite3 |
| Auth | JWT (jsonwebtoken + bcryptjs) |
| Images | GSMArena product photos |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/phones |
No | List all phones (with OS, tier) |
| POST | /api/auth/register |
No | Register new user |
| POST | /api/auth/login |
No | Login, returns JWT + membership |
| GET | /api/rentals |
JWT | Get userβs rentals |
| POST | /api/rentals |
JWT | Create rental (deposit calculated) |
| GET | /api/membership/status |
JWT | Check membership status |
| POST | /api/membership/subscribe |
JWT | Subscribe to membership |
See the Hosting Guide section below for step-by-step deployment instructions.
cd frontend
npm run build
frontend/dist/.gh-pages branch.gh-pages npm package:
npm install -D gh-pages
Add to frontend/package.json scripts:
"deploy": "gh-pages -d dist"
Then run:
npm run build && npm run deploy
Note: GitHub Pages only serves static files. You need a separate backend host.
Backend:
backendnpm install && npm run buildnode dist/index.jsJWT_SECRET=your-secret-hereFrontend:
frontendnpm install && npm run builddistVITE_API_URL=https://your-backend.onrender.combackend/, one for frontend/frontendVITE_API_URL environment variable to your Render backend URLbackend/.env)| Variable | Default | Description |
|---|---|---|
JWT_SECRET |
change-me-in-production |
JWT signing secret |
PORT |
3001 |
Server port |
| Variable | Default | Description |
|---|---|---|
VITE_API_URL |
http://localhost:3001 |
Backend API base URL |
backend/data/ folder. A fresh DB is seeded on next server start.git checkout -b feature/my-featuregit commit -m "feat: add my feature"git push origin feature/my-featureMIT