Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 91 additions & 209 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,209 +1,91 @@
# User Dashboard - Real-Time Data Implementation

## 🎯 Overview
This PR implements real-time data fetching from the Soroban smart contract for the user dashboard, replacing mock data with live on-chain information.

## 📋 Changes

### Code Files Modified (3)
- **`frontend/src/lib/contract.ts`** - Enhanced contract interaction layer
- **`frontend/src/services/meterService.ts`** - Added access checking function
- **`frontend/src/app/dashboard/user/page.tsx`** - Enhanced dashboard UI

### Key Improvements
1. **Fixed v1 Schema Compatibility**
- Updated `MeterData` interface to match contract v1 schema
- Added `version` and `expires_at` fields
- Balance now fetched separately via `get_meter_balance()`

2. **Enhanced Data Fetching**
- `fetchMeter()` now makes two contract calls:
- `get_meter(meter_id)` → meter details
- `get_meter_balance(meter_id)` → balance
- Added `checkMeterAccess()` for access verification
- Parallel fetching with `Promise.all()` for performance

3. **Improved Dashboard UI**
- Real-time balance display (XLM)
- Active/Inactive status badges (green/red)
- Units used in kWh (converted from milli-kWh)
- Plan type badges (Daily/Weekly/UsageBased)
- **NEW**: Expiry date tracking and display
- **NEW**: Warning alerts for expired plans
- **NEW**: Warning alerts for zero balance
- Smart access calculation: `active && balance > 0 && !expired`

4. **Error Handling & UX**
- Comprehensive error handling with user-friendly messages
- Loading states with skeleton cards
- Auto-refresh on wallet change
- Manual refresh button with timestamp
- Toast notifications for errors
- Retry functionality

## ✅ Acceptance Criteria

All criteria met:
- [x] Call contractQuery with meter ID on mount
- [x] Handle loading states
- [x] Handle error states
- [x] Display real balance
- [x] Display active status
- [x] Display units used
- [x] Display plan type
- [x] Refresh data on wallet change
- [x] Dashboard reflects live on-chain state

## 🧪 Testing

### Manual Testing
1. Connect Freighter wallet
2. Verify meter data loads from contract
3. Check balance, status, units, plan display correctly
4. Test refresh button
5. Test wallet disconnect/reconnect
6. Verify error handling (offline mode)

### Test Coverage
- 38+ test cases documented in `TESTING_CHECKLIST.md`
- Functional tests, edge cases, responsive design
- Browser compatibility, accessibility, security

## 📚 Documentation

Comprehensive documentation included:
- **`README_DASHBOARD_UPDATE.md`** - Main overview
- **`QUICK_START_DASHBOARD.md`** - Quick reference
- **`DASHBOARD_IMPLEMENTATION.md`** - Technical details
- **`TESTING_CHECKLIST.md`** - 38+ test cases
- **`ARCHITECTURE_DIAGRAM.md`** - Visual architecture
- **`IMPLEMENTATION_SUMMARY.md`** - Executive summary
- **`FILES_CHANGED.md`** - Change summary
- **`COMPLETION_REPORT.md`** - Project report

Total: 2,650+ lines of documentation

## 🔧 Technical Details

### Contract Queries Used
```typescript
// 1. Get meter IDs for owner
get_meters_by_owner(address) → Vec<Symbol>

// 2. Get meter details
get_meter(meter_id) → Meter {
version, owner, active, units_used,
plan, last_payment, expires_at
}

// 3. Get balance separately (v1 schema)
get_meter_balance(meter_id) → i128
```

### Data Flow
```
User connects wallet
getMetersByOwner(address)
For each meter:
getMeter(meterId)
├─ get_meter(meter_id)
└─ get_meter_balance(meter_id)
Display in MeterCard
```

## 🔐 Security

- ✅ Read-only queries use throwaway keypairs
- ✅ No private keys exposed
- ✅ Wallet signature only for write operations
- ✅ Input validation on all queries
- ✅ Error messages sanitized

## 📊 Performance

- **RPC Calls**: 1 + (2 × N) for N meters
- **Example**: 3 meters = 7 calls (~2 seconds)
- **Optimization**: Parallel fetching with `Promise.all()`

## 🎨 Screenshots

### Before
- Hardcoded mock data
- No expiry tracking
- No warnings

### After
- Live contract data
- Expiry date display
- Smart warnings (expired/zero balance)
- Auto-refresh on wallet change

## 🚀 Deployment

### Environment Variables Required
```env
NEXT_PUBLIC_CONTRACT_ID=<deployed_contract_id>
NEXT_PUBLIC_RPC_URL=https://soroban-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
```

### Build
```bash
cd frontend
npm run build
```

## 📝 Checklist

- [x] Code implemented
- [x] TypeScript errors resolved (0 errors)
- [x] ESLint warnings resolved (0 warnings)
- [x] Documentation created
- [x] Testing guide provided
- [x] Architecture documented
- [x] Security reviewed
- [x] Performance optimized
- [ ] Code review (pending)
- [ ] QA testing (pending)

## 🎯 Breaking Changes

None. This is a pure enhancement that maintains backward compatibility.

## 🔄 Rollback Plan

If issues occur, revert these 3 files:
- `frontend/src/lib/contract.ts`
- `frontend/src/services/meterService.ts`
- `frontend/src/app/dashboard/user/page.tsx`

## 📖 Related Documentation

- Start with: `README_DASHBOARD_UPDATE.md`
- Quick reference: `QUICK_START_DASHBOARD.md`
- Testing: `TESTING_CHECKLIST.md`
- Technical: `DASHBOARD_IMPLEMENTATION.md`

## 🙏 Review Notes

This PR includes:
- **135 lines** of code changes (3 files)
- **2,650+ lines** of documentation (8 files)
- **38+ test cases** documented
- **Zero** TypeScript errors
- **Senior-level** code quality

Please review:
1. Contract interaction logic in `contract.ts`
2. UI enhancements in `page.tsx`
3. Error handling throughout
4. Documentation completeness

## 🎉 Result

Production-ready user dashboard with real-time Soroban contract data!

All acceptance criteria met (9/9) with 10+ bonus features and comprehensive documentation.
## Summary
This PR delivers two user-facing improvements:

1. Proactive low-balance browser push notifications for meter owners.
2. A pagination reliability fix for payment history (including Safari behavior where paging could stop responding after deep scroll).

## Problem
Users only discovered low balances after manually checking dashboards, causing avoidable service interruptions. In addition, payment history pagination could become non-functional after scrolling in Safari, with no API request fired and URL page state not updating consistently.

## What Changed
### Frontend
- Added Web Push client setup service:
- Requests notification permission on first dashboard visit.
- Registers service worker and subscribes browser via PushManager.
- Persists subscription endpoint in localStorage to avoid duplicate registration calls.
- Added service worker to:
- Render push notifications.
- Handle notification click/action and route users to top-up flow.
- Added push notification icons.
- Updated User Dashboard polling flow to trigger subscription registration once low-balance condition is observed.
- Fixed payment history pagination flow:
- Uses URL page param as the source of truth.
- Uses explicit page-change handler for next/prev.
- Scrolls to top of history section on page change.
- Prevents stale disabled-state behavior on pagination buttons.
- Fixed a pre-existing production build failure:
- `/history` and `/dashboard/provider` used `useSearchParams()` without a `Suspense` boundary, which made `next build` fail with a prerender/export error (unrelated to this PR's business logic, but blocking any build that touches these routes). Wrapped both pages' bodies in `Suspense` so the build succeeds again.

### Backend
- Added push subscription persistence layer (SQLite-backed table):
- Upsert subscriptions by endpoint.
- Delete stale/unsubscribed endpoints.
- Added Web Push delivery module using VAPID configuration.
- Added new push API endpoints:
- `GET /api/push/config`
- `POST /api/push/subscribe`
- `POST /api/push/unsubscribe`
- Integrated low-balance push send in IoT bridge low-balance pipeline.
- Upgraded low-balance threshold logic to requested rule:
- `threshold = 10% of typical weekly usage (last 7 days)`
- Falls back to `LOW_BALANCE_THRESHOLD` when insufficient history exists.
- Exposed low-balance metadata in meter balance responses for frontend gating.

### Docs / Config
- Updated backend API docs to include push endpoints and dynamic threshold behavior.
- Added required/optional Web Push env vars to backend `.env.example`.
- Added frontend backend URL env example for push API usage.

## Files of Interest
- Frontend:
- `src/app/history/page.tsx`
- `src/app/dashboard/user/page.tsx`
- `src/services/pushService.ts`
- `public/sw.js`
- `public/icons/push-warning.svg`
- `public/icons/push-badge.svg`
- Backend:
- `../backend/src/routes/pushSubscriptions.ts`
- `../backend/src/lib/pushNotifications.ts`
- `../backend/src/lib/pushSubscriptions.ts`
- `../backend/src/iot/bridge.ts`
- `../backend/src/lib/usageEvents.ts`
- `../backend/src/routes/meters.ts`
- `../backend/src/index.ts`

## Testing / Validation
- Backend TypeScript build passes: `cd backend && npm run build`.
- Backend dependencies verified installed and loadable (`web-push`).
- Frontend type check passes for all application source (`cd frontend && npx tsc --noEmit`, excluding pre-existing test-file type errors from missing Jest type defs, unrelated to this change).
- Frontend production build passes end-to-end (`cd frontend && npm run build`, exit code 0) after the Suspense fix.
- `frontend/npm test` run: pre-existing failures in `OfflinePaymentModal.test.tsx` and `AllowlistPanel.test.tsx` (missing unrelated modules `@/hooks/useOffline`, `@/services/allowlistService`) are unaffected by this change; all other suites pass (19/19 tests).

## Operational Notes
- To enable push notifications in deployed environments, set:
- `WEB_PUSH_VAPID_SUBJECT`
- `WEB_PUSH_VAPID_PUBLIC_KEY`
- `WEB_PUSH_VAPID_PRIVATE_KEY`
- Without these vars, push endpoints remain available but sending is effectively disabled (safe no-op behavior with warning logs).

## Risk Assessment
- Low-to-medium risk due to new notification pipeline and persistence table.
- Mitigations:
- Invalid/stale subscriptions are removed on 404/410 push responses.
- Existing webhook path remains intact.
- Fallback threshold preserves behavior when history data is unavailable.

## Follow-ups
- Add targeted unit/integration tests for:
- Push subscription route validation.
- Threshold computation edge cases.
- Safari regression on pagination URL/button state.
5 changes: 5 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ MQTT_MAX_RECONNECT_ATTEMPTS=10
PROVIDER_WEBHOOK_URL=https://example.com/webhook
LOW_BALANCE_THRESHOLD=1000000

# Web Push configuration (optional; required to enable browser push notifications)
WEB_PUSH_VAPID_SUBJECT=mailto:alerts@example.com
WEB_PUSH_VAPID_PUBLIC_KEY=YOUR_VAPID_PUBLIC_KEY
WEB_PUSH_VAPID_PRIVATE_KEY=YOUR_VAPID_PRIVATE_KEY

# CORS — comma-separated allowed origins. Use * to allow all (default for dev).
CORS_ORIGIN=http://localhost:5173,https://app.stellarsolargrid.io

Expand Down
46 changes: 42 additions & 4 deletions backend/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ Set the following environment variables:
| Variable | Required | Default | Description |
| ----------------------- | -------- | ------- | ---------------------------------------------- |
| `PROVIDER_WEBHOOK_URL` | No | - | Webhook endpoint URL for low-balance alerts |
| `LOW_BALANCE_THRESHOLD` | No | 1000000 | Balance threshold in stroops (0.1 XLM default) |
| `LOW_BALANCE_THRESHOLD` | No | 1000000 | Fallback threshold (used when no 7-day usage history exists) |
| `WEB_PUSH_VAPID_SUBJECT` | No | - | VAPID subject for Web Push (`mailto:...`) |
| `WEB_PUSH_VAPID_PUBLIC_KEY` | No | - | VAPID public key sent to browsers |
| `WEB_PUSH_VAPID_PRIVATE_KEY` | No | - | VAPID private key used to sign push sends |

### Register Webhook Endpoint

Expand Down Expand Up @@ -249,7 +252,11 @@ A request without a valid `X-Admin-Key` header returns `401 Unauthorized`.

### Webhook Payload

When a meter's balance drops below the threshold after a usage update, the bridge fires a POST request to the registered webhook URL.
When a meter's balance drops below the alert threshold after a usage update, the bridge fires a POST request to the registered webhook URL.

Alert threshold rule:
- `threshold = 10% of typical weekly usage (last 7 days summed cost)`
- If no recent usage exists, fallback to `LOW_BALANCE_THRESHOLD`

**Payload**

Expand All @@ -258,7 +265,8 @@ When a meter's balance drops below the threshold after a usage update, the bridg
"event": "low_balance",
"meter_id": "METER123",
"balance": 500000,
"threshold": 1000000,
"threshold": 800000,
"weekly_typical_stroops": 8000000,
"timestamp": "2025-05-27T10:30:00.000Z"
}
```
Expand All @@ -270,9 +278,39 @@ When a meter's balance drops below the threshold after a usage update, the bridg
| `event` | string | Always `"low_balance"` |
| `meter_id` | string | The meter identifier |
| `balance` | number | Current meter balance in stroops |
| `threshold` | number | Configured threshold in stroops |
| `threshold` | number | Computed threshold in stroops |
| `weekly_typical_stroops` | number | Last-7-days usage cost sum in stroops |
| `timestamp` | string | ISO 8601 timestamp of the event |

## Push Subscription API

### `GET /api/push/config`

Returns push feature status and the VAPID public key for browser subscription.

### `POST /api/push/subscribe`

Stores/updates a browser push subscription for a Stellar owner address.

Body:

```json
{
"ownerAddress": "G...",
"subscription": {
"endpoint": "https://...",
"keys": {
"p256dh": "...",
"auth": "..."
}
}
}
```

### `POST /api/push/unsubscribe`

Deletes a stored push subscription by endpoint.

**Error Handling**

- Failed webhook calls are logged but do not crash the IoT bridge
Expand Down
Loading
Loading