Skip to content

Commit 0e81a33

Browse files
committed
Add dev/prod environment separation with collection prefixes
- Environment auto-detection (localhost=dev, deployed=prod) - Dev collections prefixed with 'dev_' (e.g., dev_items, dev_purchases) - Users collection shared between dev/prod (same auth roles) - Visual 'DEVELOPMENT' banner in dev mode - Updated Firestore rules for dev_* collections - Fixed modal HTML: changed <p> to <div> for complex content - Added ENVIRONMENT_SETUP.md documentation
1 parent 7ba7da1 commit 0e81a33

13 files changed

Lines changed: 331 additions & 74 deletions

File tree

ENVIRONMENT_SETUP.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Environment Separation Guide
2+
3+
## Overview
4+
5+
This app uses **collection prefixes** to separate development and production data within the **same Firebase project** (no extra project needed!):
6+
7+
| Environment | Collection Names | Example |
8+
|-------------|------------------|---------|
9+
| `production` | `items`, `purchases`, etc. | Normal collections |
10+
| `development` | `dev_items`, `dev_purchases`, etc. | Prefixed collections |
11+
12+
## How It Works
13+
14+
### Automatic Detection
15+
16+
The app automatically detects the environment:
17+
18+
1. **URL Parameter** (highest priority): `?env=development`
19+
2. **localStorage**: `localStorage.setItem('appEnv', 'development')`
20+
3. **Hostname**: `localhost` → development, otherwise → production
21+
22+
### Visual Indicator
23+
24+
When running in development mode, you'll see an orange banner at the top of the screen showing "🔧 DEVELOPMENT".
25+
26+
## Usage
27+
28+
### For Development (testing locally)
29+
30+
```bash
31+
# Just run locally - automatically uses dev_ prefix (localhost = development)
32+
npx serve www
33+
```
34+
35+
All data will be stored in `dev_items`, `dev_purchases`, `dev_retailSales`, etc.
36+
37+
### For Production (deployed app)
38+
39+
The deployed app at `aadhat-management.web.app` automatically uses production (no prefix).
40+
41+
### Force Development on Production URL (for testing)
42+
43+
```bash
44+
# One-time: only affects this page load
45+
https://aadhat-management.web.app/?env=development
46+
47+
# Persistent: set in browser console, persists across sessions
48+
localStorage.setItem('appEnv', 'development');
49+
```
50+
51+
### Switch Environments Manually
52+
53+
```javascript
54+
// In browser console:
55+
56+
// Switch to development
57+
localStorage.setItem('appEnv', 'development');
58+
location.reload();
59+
60+
// Switch to production
61+
localStorage.setItem('appEnv', 'production');
62+
location.reload();
63+
64+
// Clear override (use automatic detection)
65+
localStorage.removeItem('appEnv');
66+
location.reload();
67+
68+
// Check current environment
69+
console.log(window.APP_ENV); // 'development' or 'production'
70+
console.log(window.COLLECTION_PREFIX); // 'dev_' or ''
71+
```
72+
73+
## Benefits of This Approach
74+
75+
**No extra Firebase project needed** - Uses same project, different collections
76+
**Free** - No additional costs
77+
**Same auth** - Login works in both environments
78+
**Easy cleanup** - Just delete `dev_*` collections when done testing
79+
**Clear separation** - Production data is never touched during development
80+
81+
## Viewing Dev Data in Firebase Console
82+
83+
In Firebase Console, you'll see both sets of collections:
84+
- `items` (production)
85+
- `dev_items` (development)
86+
- `purchases` (production)
87+
- `dev_purchases` (development)
88+
- etc.
89+
90+
## Cleanup Dev Data
91+
92+
To delete all development data, go to Firebase Console and delete all collections starting with `dev_`.
93+
94+
Or use the app's "Clear All Data" feature while in development mode.
95+
96+
## Quick Reference
97+
98+
| Command | Effect |
99+
|---------|--------|
100+
| `?env=development` | Force dev environment |
101+
| `?env=production` | Force prod environment |
102+
| `localStorage.setItem('appEnv', 'development')` | Persist dev mode |
103+
| `window.APP_ENV` | Check current environment |
104+
| `window.COLLECTION_PREFIX` | Check current prefix (`dev_` or ``) |
105+
| `window.getCollection('items')` | Get prefixed collection name |

firestore.rules

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,59 @@ service cloud.firestore {
108108
allow read: if isSignedIn() && isAdmin();
109109
allow update, delete: if false; // Never allow modification or deletion
110110
}
111+
112+
// ============================================
113+
// DEVELOPMENT ENVIRONMENT COLLECTIONS (dev_ prefix)
114+
// Mirror all production rules for dev collections
115+
// ============================================
116+
117+
match /dev_purchases/{docId} {
118+
allow read, write: if isSignedIn();
119+
}
120+
match /dev_retailSales/{docId} {
121+
allow read, write: if isSignedIn();
122+
}
123+
match /dev_wholesaleSales/{docId} {
124+
allow read, write: if isSignedIn();
125+
}
126+
match /dev_items/{docId} {
127+
allow read, write: if isSignedIn();
128+
}
129+
match /dev_expenses/{docId} {
130+
allow read, write: if isSignedIn();
131+
}
132+
match /dev_stockAdjustments/{docId} {
133+
allow read, write: if isSignedIn();
134+
}
135+
match /dev_withdrawals/{docId} {
136+
allow read, write: if isSignedIn();
137+
}
138+
match /dev_cashManagement/{docId} {
139+
allow read, write: if isSignedIn();
140+
}
141+
match /dev_cashSessions/{docId} {
142+
allow read, write: if isSignedIn();
143+
}
144+
match /dev_users/{docId} {
145+
allow read, write: if isSignedIn();
146+
}
147+
match /dev_itemFrequency/{docId} {
148+
allow read, write: if isSignedIn();
149+
}
150+
match /dev_notifications/{docId} {
151+
allow read, write: if isSignedIn();
152+
}
153+
match /dev_autoSaves/{docId} {
154+
allow read, write: if isSignedIn();
155+
}
156+
match /dev_drafts/{docId} {
157+
allow read, write: if isSignedIn();
158+
}
159+
match /dev_auditLogs/{docId} {
160+
allow read, write: if isSignedIn();
161+
}
162+
match /dev_settings/{docId} {
163+
allow read, write: if isSignedIn();
164+
}
111165
}
112166
}

www/css/charts.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ body:has(.tab.sale-theme.active) .modal-overlay .modal-header {
192192
flex: 1;
193193
}
194194

195-
.modal-body p {
195+
#modalMessage {
196196
margin: 0;
197197
white-space: pre-line;
198198
}

www/firebaseConfig.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,49 @@
66
// 2. Setting up HTTP referrer restrictions
77
// 3. Using Firebase App Check for additional security
88

9+
// =============================================================================
10+
// ENVIRONMENT CONFIGURATION
11+
// =============================================================================
12+
// Set to 'development' for local testing with dummy data
13+
// Set to 'production' for live app with real data
14+
//
15+
// Option 1: Use URL parameter: ?env=development
16+
// Option 2: Use localStorage: localStorage.setItem('appEnv', 'development')
17+
// Option 3: Automatic: localhost = development, otherwise = production
18+
// =============================================================================
19+
20+
// Check environment from multiple sources
21+
const getEnvironment = () => {
22+
// 1. Check URL parameter (highest priority for quick testing)
23+
const urlParams = new URLSearchParams(window.location.search);
24+
if (urlParams.get('env')) return urlParams.get('env');
25+
26+
// 2. Check localStorage (persists across sessions)
27+
if (localStorage.getItem('appEnv')) return localStorage.getItem('appEnv');
28+
29+
// 3. Check if running on localhost
30+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
31+
return 'development';
32+
}
33+
34+
// 4. Default to production
35+
return 'production';
36+
};
37+
38+
const APP_ENV = getEnvironment();
39+
40+
// Collection prefix based on environment
41+
// Production: 'items', 'purchases', etc.
42+
// Development: 'dev_items', 'dev_purchases', etc.
43+
const COLLECTION_PREFIX = APP_ENV === 'production' ? '' : 'dev_';
44+
45+
// Collections that should NOT be prefixed (shared between dev and prod)
46+
// - users: Keep same user roles/permissions in dev
47+
const SHARED_COLLECTIONS = ['users'];
48+
49+
console.log(`🔧 App Environment: ${APP_ENV} | Collection Prefix: "${COLLECTION_PREFIX}"`);
50+
51+
// Single Firebase Config (same project for both environments)
952
const firebaseConfig = {
1053
apiKey: "AIzaSyD0ib9JkKbqaNE2i_TZlXYJqEtXI_i2Fj8",
1154
authDomain: "aadhat-management.firebaseapp.com",
@@ -16,6 +59,43 @@ const firebaseConfig = {
1659
measurementId: "G-01155M3XPL"
1760
};
1861

62+
// Expose environment globally
63+
window.APP_ENV = APP_ENV;
64+
window.COLLECTION_PREFIX = COLLECTION_PREFIX;
65+
66+
// Helper function to get prefixed collection name
67+
// Some collections are shared (not prefixed) between dev and prod
68+
window.getCollection = (name) => {
69+
if (SHARED_COLLECTIONS.includes(name)) {
70+
return name; // No prefix for shared collections
71+
}
72+
return COLLECTION_PREFIX + name;
73+
};
74+
75+
// Show environment indicator in UI (for development only)
76+
if (APP_ENV !== 'production') {
77+
document.addEventListener('DOMContentLoaded', () => {
78+
const indicator = document.createElement('div');
79+
indicator.id = 'env-indicator';
80+
indicator.innerHTML = `🔧 ${APP_ENV.toUpperCase()}`;
81+
indicator.style.cssText = `
82+
position: fixed;
83+
top: 0;
84+
left: 50%;
85+
transform: translateX(-50%);
86+
background: #ff6b35;
87+
color: white;
88+
padding: 2px 12px;
89+
font-size: 10px;
90+
font-weight: bold;
91+
z-index: 99999;
92+
border-radius: 0 0 4px 4px;
93+
text-transform: uppercase;
94+
`;
95+
document.body.appendChild(indicator);
96+
});
97+
}
98+
1999
// Initialize Firebase
20100
let app;
21101
try {

www/js/auth/authentication.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const AuthManager = {
8888
const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
8989

9090
const userId = userCredential.user.uid;
91-
const userDoc = await firebase.firestore().collection('users').doc(userId).get();
91+
const userDoc = await firebase.firestore().collection(window.getCollection ? window.getCollection('users') : 'users').doc(userId).get();
9292

9393
if (!userDoc.exists) {
9494
await firebase.auth().signOut();
@@ -191,7 +191,7 @@ const AuthManager = {
191191
const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, password);
192192
const userId = userCredential.user.uid;
193193

194-
await firebase.firestore().collection('users').doc(userId).set({
194+
await firebase.firestore().collection(window.getCollection ? window.getCollection('users') : 'users').doc(userId).set({
195195
name: name,
196196
email: email,
197197
role: role,

0 commit comments

Comments
 (0)