Skip to content

Commit 9f8672e

Browse files
authored
feat: add demo mode (#13)
1 parent ac78c9d commit 9f8672e

13 files changed

Lines changed: 913 additions & 48 deletions

File tree

‎web/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules
22
dist
3+
dist-demo
34
build
45
.vite
56
*.log

‎web/README.md‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ npm run dev
3636

3737
The application will be available at `http://localhost:3000`. API requests will be proxied to the backend at `http://localhost:8080`.
3838

39+
### Demo Mode
40+
41+
For development or demonstration purposes without a backend server, you can run the application in demo mode. This mode uses realistic mock data that simulates all backend functionality:
42+
43+
```bash
44+
npm run dev:demo
45+
```
46+
47+
In demo mode:
48+
- No backend connection is required
49+
- Realistic simulated data updates every 10 seconds
50+
- Solar power, battery, grid, and device data reflect realistic daily patterns
51+
- Electricity prices vary based on time of day
52+
- MPC decisions are generated for the next 24 hours
53+
- All UI components work exactly as they would with a real backend
54+
3955
## Production Build
4056

4157
Build the application for production:
@@ -46,6 +62,27 @@ npm run build
4662

4763
The built files will be in the `dist` directory, which the Go backend serves at the root path.
4864

65+
### Demo Build
66+
67+
Build a standalone demo version that doesn't require a backend:
68+
69+
```bash
70+
npm run build:demo
71+
```
72+
73+
The demo build will be in the `dist-demo` directory. You can preview it with:
74+
75+
```bash
76+
npm run preview:demo
77+
```
78+
79+
The demo build is useful for:
80+
- Demonstrations and presentations
81+
- UI/UX testing without backend setup
82+
- Showcasing the application to stakeholders
83+
- Development when backend is unavailable
84+
- Static hosting scenarios
85+
4986
## Project Structure
5087

5188
```
@@ -115,10 +152,11 @@ The web application consumes the following API endpoints:
115152
The Vite configuration includes:
116153

117154
- Development server on port 3000
118-
- API proxy to backend on port 8080
119-
- WebSocket proxy for real-time updates
120-
- Production build output to `dist/`
155+
- API proxy to backend on port 8080 (disabled in demo mode)
156+
- WebSocket proxy for real-time updates (mock data in demo mode)
157+
- Production build output to `dist/` (or `dist-demo/` for demo builds)
121158
- Source maps enabled
159+
- Conditional compilation for demo mode via `__DEMO_MODE__` flag
122160

123161
## Customization
124162

@@ -211,6 +249,8 @@ Historical data can be visualized with charts showing:
211249
- Enable React DevTools for component inspection
212250
- Check console for WebSocket connection status
213251
- Use network tab to debug API issues
252+
- Use demo mode for frontend-only development
253+
- Mock data generator can be customized in `src/utils/mockData.ts`
214254

215255
## License
216256

‎web/package.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
"type": "module",
77
"scripts": {
88
"dev": "vite",
9+
"dev:demo": "vite --mode demo",
910
"build": "tsc && vite build",
10-
"preview": "vite preview"
11+
"build:demo": "tsc && vite build --mode demo",
12+
"preview": "vite preview",
13+
"preview:demo": "vite preview --outDir dist-demo"
1114
},
1215
"dependencies": {
1316
"react": "^18.2.0",

‎web/src/App.css‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@ body {
9090
border: 1px solid var(--color-warning);
9191
}
9292

93+
.demo-info-trigger {
94+
padding: 0.5rem 0.75rem;
95+
border-radius: var(--border-radius);
96+
font-weight: 600;
97+
font-size: 0.875rem;
98+
background-color: rgba(37, 99, 235, 0.2);
99+
color: var(--color-primary);
100+
border: 1px solid var(--color-primary);
101+
cursor: pointer;
102+
transition: all 0.2s;
103+
}
104+
105+
.demo-info-trigger:hover {
106+
background-color: rgba(37, 99, 235, 0.3);
107+
transform: scale(1.05);
108+
}
109+
110+
.demo-info-trigger:active {
111+
transform: scale(0.98);
112+
}
113+
93114
.main {
94115
flex: 1;
95116
padding: 2rem;

‎web/src/App.tsx‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,27 @@ import { PowerDisplay } from "./components/PowerDisplay";
66
import { SolarInfo } from "./components/SolarInfo";
77
import { MPCDecisions } from "./components/MPCDecisions";
88
import { MetricsSummary } from "./components/MetricsSummary";
9+
import { DemoInfo } from "./components/DemoInfo";
910
import { useWebSocket } from "./hooks/useWebSocket";
11+
import { useState, useEffect } from "react";
12+
13+
// Check if we're in demo mode
14+
const isDemoMode = typeof __DEMO_MODE__ !== 'undefined' && __DEMO_MODE__;
1015

1116
function App() {
1217
const { health, status, loading, error, wsConnected } = useWebSocket();
18+
const [showDemoInfo, setShowDemoInfo] = useState(false);
19+
20+
// Show demo info automatically on first load in demo mode
21+
useEffect(() => {
22+
if (isDemoMode) {
23+
const hasSeenDemo = localStorage.getItem('ems-demo-info-seen');
24+
if (!hasSeenDemo) {
25+
setShowDemoInfo(true);
26+
localStorage.setItem('ems-demo-info-seen', 'true');
27+
}
28+
}
29+
}, []);
1330

1431
if (loading) {
1532
return (
@@ -50,6 +67,22 @@ function App() {
5067
Energy Management System
5168
</h1>
5269
<div className="status-badges">
70+
{isDemoMode && (
71+
<StatusBadge
72+
isActive={true}
73+
activeLabel="🎭 Demo Mode"
74+
inactiveLabel=""
75+
/>
76+
)}
77+
{isDemoMode && (
78+
<button
79+
className="demo-info-trigger"
80+
onClick={() => setShowDemoInfo(true)}
81+
title="Learn about Demo Mode"
82+
>
83+
ℹ️
84+
</button>
85+
)}
5386
<StatusBadge
5487
isActive={isHealthy}
5588
activeLabel="✓ Healthy"
@@ -246,6 +279,10 @@ function App() {
246279
available power
247280
</p>
248281
</footer>
282+
283+
{isDemoMode && showDemoInfo && (
284+
<DemoInfo onClose={() => setShowDemoInfo(false)} />
285+
)}
249286
</div>
250287
);
251288
}

‎web/src/components/DemoInfo.css‎

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
.demo-info-overlay {
2+
position: fixed;
3+
top: 0;
4+
left: 0;
5+
right: 0;
6+
bottom: 0;
7+
background-color: rgba(0, 0, 0, 0.75);
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
z-index: 1000;
12+
backdrop-filter: blur(4px);
13+
animation: fadeIn 0.2s ease-out;
14+
}
15+
16+
@keyframes fadeIn {
17+
from {
18+
opacity: 0;
19+
}
20+
to {
21+
opacity: 1;
22+
}
23+
}
24+
25+
.demo-info-card {
26+
background-color: var(--color-bg-card);
27+
border-radius: var(--border-radius);
28+
padding: 2rem;
29+
max-width: 600px;
30+
width: 90%;
31+
max-height: 90vh;
32+
overflow-y: auto;
33+
border: 2px solid var(--color-primary);
34+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
35+
position: relative;
36+
animation: slideUp 0.3s ease-out;
37+
}
38+
39+
@keyframes slideUp {
40+
from {
41+
transform: translateY(20px);
42+
opacity: 0;
43+
}
44+
to {
45+
transform: translateY(0);
46+
opacity: 1;
47+
}
48+
}
49+
50+
.demo-info-close {
51+
position: absolute;
52+
top: 1rem;
53+
right: 1rem;
54+
background: transparent;
55+
border: none;
56+
color: var(--color-text-secondary);
57+
font-size: 1.5rem;
58+
cursor: pointer;
59+
width: 2rem;
60+
height: 2rem;
61+
display: flex;
62+
align-items: center;
63+
justify-content: center;
64+
border-radius: 4px;
65+
transition: all 0.2s;
66+
}
67+
68+
.demo-info-close:hover {
69+
background-color: rgba(255, 255, 255, 0.1);
70+
color: var(--color-text);
71+
}
72+
73+
.demo-info-card h2 {
74+
font-size: 1.75rem;
75+
font-weight: 700;
76+
color: var(--color-text);
77+
margin-bottom: 1rem;
78+
border-bottom: 2px solid var(--color-primary);
79+
padding-bottom: 0.75rem;
80+
}
81+
82+
.demo-info-description {
83+
font-size: 1rem;
84+
color: var(--color-text-secondary);
85+
margin-bottom: 1.5rem;
86+
line-height: 1.6;
87+
}
88+
89+
.demo-info-section {
90+
margin-bottom: 1.5rem;
91+
}
92+
93+
.demo-info-section h3 {
94+
font-size: 1.125rem;
95+
font-weight: 600;
96+
color: var(--color-text);
97+
margin-bottom: 0.75rem;
98+
}
99+
100+
.demo-info-section p {
101+
font-size: 0.9375rem;
102+
color: var(--color-text-secondary);
103+
line-height: 1.6;
104+
margin-bottom: 0.5rem;
105+
}
106+
107+
.demo-info-section ul {
108+
list-style: none;
109+
padding: 0;
110+
margin: 0;
111+
}
112+
113+
.demo-info-section li {
114+
font-size: 0.9375rem;
115+
color: var(--color-text-secondary);
116+
padding: 0.5rem 0;
117+
padding-left: 0.5rem;
118+
line-height: 1.5;
119+
border-left: 3px solid var(--color-primary);
120+
margin-bottom: 0.5rem;
121+
background-color: rgba(37, 99, 235, 0.05);
122+
border-radius: 0 4px 4px 0;
123+
}
124+
125+
.demo-info-note {
126+
background-color: rgba(234, 88, 12, 0.1);
127+
border: 1px solid var(--color-warning);
128+
border-radius: 6px;
129+
padding: 1rem;
130+
margin-bottom: 1.5rem;
131+
font-size: 0.875rem;
132+
line-height: 1.6;
133+
color: var(--color-text-secondary);
134+
}
135+
136+
.demo-info-note strong {
137+
color: var(--color-warning);
138+
font-weight: 600;
139+
}
140+
141+
.demo-info-note code {
142+
background-color: rgba(0, 0, 0, 0.3);
143+
padding: 0.125rem 0.375rem;
144+
border-radius: 3px;
145+
font-family: "Courier New", monospace;
146+
font-size: 0.8125rem;
147+
color: var(--color-text);
148+
}
149+
150+
.demo-info-button {
151+
width: 100%;
152+
background-color: var(--color-primary);
153+
color: white;
154+
border: none;
155+
padding: 0.75rem 1.5rem;
156+
border-radius: 6px;
157+
font-size: 1rem;
158+
font-weight: 600;
159+
cursor: pointer;
160+
transition: all 0.2s;
161+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
162+
}
163+
164+
.demo-info-button:hover {
165+
background-color: #1d4ed8;
166+
transform: translateY(-1px);
167+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
168+
}
169+
170+
.demo-info-button:active {
171+
transform: translateY(0);
172+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
173+
}
174+
175+
@media (max-width: 768px) {
176+
.demo-info-card {
177+
padding: 1.5rem;
178+
width: 95%;
179+
}
180+
181+
.demo-info-card h2 {
182+
font-size: 1.5rem;
183+
}
184+
185+
.demo-info-section h3 {
186+
font-size: 1rem;
187+
}
188+
189+
.demo-info-section p,
190+
.demo-info-section li {
191+
font-size: 0.875rem;
192+
}
193+
}

0 commit comments

Comments
 (0)