WellNuo/app/(tabs)/bug.tsx
Sergei e74d1a4b26 Show user role under beneficiary name
- Added role field to Beneficiary type
- Display role (Custodian/Guardian/Caretaker) in small gray text under name
- Role comes from user_access table via API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 19:08:12 -08:00

324 lines
8.2 KiB
TypeScript

import React, { useRef } from 'react';
import { View, StyleSheet, SafeAreaView } from 'react-native';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { useRouter } from 'expo-router';
import { AppColors } from '@/constants/theme';
// Test HTML page with buttons that send messages to React Native
const TEST_HTML = `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: white;
}
.container {
max-width: 400px;
margin: 0 auto;
}
h1 {
font-size: 24px;
margin-bottom: 10px;
text-align: center;
}
.subtitle {
font-size: 14px;
opacity: 0.8;
text-align: center;
margin-bottom: 30px;
}
.card {
background: rgba(255,255,255,0.15);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 20px;
margin-bottom: 16px;
}
.card-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
}
.btn {
display: block;
width: 100%;
padding: 16px;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
margin-bottom: 12px;
transition: transform 0.1s, opacity 0.1s;
}
.btn:active {
transform: scale(0.98);
opacity: 0.9;
}
.btn-primary {
background: white;
color: #667eea;
}
.btn-secondary {
background: rgba(255,255,255,0.2);
color: white;
border: 2px solid rgba(255,255,255,0.3);
}
.btn-danger {
background: #ff6b6b;
color: white;
}
.log {
background: rgba(0,0,0,0.2);
border-radius: 8px;
padding: 12px;
font-family: monospace;
font-size: 12px;
max-height: 150px;
overflow-y: auto;
margin-top: 20px;
}
.log-entry {
margin-bottom: 4px;
opacity: 0.9;
}
</style>
</head>
<body>
<div class="container">
<h1>WebView Bridge Test</h1>
<p class="subtitle">Test communication between Web and React Native</p>
<div class="card">
<div class="card-title">Navigation Commands</div>
<button class="btn btn-primary" onclick="navigateTo('beneficiaries')">
Open Beneficiaries
</button>
<button class="btn btn-secondary" onclick="navigateTo('chat')">
Open Chat
</button>
<button class="btn btn-secondary" onclick="navigateTo('profile')">
Open Profile
</button>
</div>
<div class="card">
<div class="card-title">Native Features</div>
<button class="btn btn-primary" onclick="requestNativeAction('bluetooth')">
Scan Bluetooth Devices
</button>
<button class="btn btn-secondary" onclick="requestNativeAction('camera')">
Open Camera
</button>
</div>
<div class="card">
<div class="card-title">Send Custom Data</div>
<button class="btn btn-danger" onclick="sendCustomMessage()">
Send Test Message
</button>
</div>
<div class="log" id="log">
<div class="log-entry">Ready to communicate...</div>
</div>
</div>
<script>
function log(message) {
const logEl = document.getElementById('log');
const time = new Date().toLocaleTimeString();
logEl.innerHTML += '<div class="log-entry">[' + time + '] ' + message + '</div>';
logEl.scrollTop = logEl.scrollHeight;
}
function sendToRN(data) {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify(data));
log('Sent: ' + JSON.stringify(data));
} else {
log('ERROR: Not in WebView');
alert('This page must be opened in the mobile app');
}
}
function navigateTo(screen) {
sendToRN({
action: 'NAVIGATE',
screen: screen
});
}
function requestNativeAction(feature) {
sendToRN({
action: 'NATIVE_FEATURE',
feature: feature
});
}
function sendCustomMessage() {
sendToRN({
action: 'CUSTOM',
payload: {
timestamp: Date.now(),
message: 'Hello from WebView!',
data: { foo: 'bar', count: 42 }
}
});
}
// Listen for messages FROM React Native
window.addEventListener('message', function(event) {
try {
const data = JSON.parse(event.data);
log('Received from RN: ' + JSON.stringify(data));
} catch (e) {
log('Received: ' + event.data);
}
});
// Also handle React Native's onMessage format
document.addEventListener('message', function(event) {
try {
const data = JSON.parse(event.data);
log('Received from RN: ' + JSON.stringify(data));
} catch (e) {
log('Received: ' + event.data);
}
});
log('WebView Bridge initialized');
</script>
</body>
</html>
`;
export default function BugScreen() {
const router = useRouter();
const webViewRef = useRef<WebView>(null);
// Handle messages from WebView
const handleMessage = (event: WebViewMessageEvent) => {
try {
const data = JSON.parse(event.nativeEvent.data);
console.log('[Bug WebView] Received message:', data);
switch (data.action) {
case 'NAVIGATE':
handleNavigation(data.screen);
break;
case 'NATIVE_FEATURE':
handleNativeFeature(data.feature);
break;
case 'CUSTOM':
handleCustomMessage(data.payload);
break;
default:
console.log('[Bug WebView] Unknown action:', data.action);
}
} catch (error) {
console.error('[Bug WebView] Error parsing message:', error);
}
};
// Navigate to different screens
const handleNavigation = (screen: string) => {
console.log('[Bug WebView] Navigating to:', screen);
switch (screen) {
case 'beneficiaries':
router.push('/(tabs)/');
break;
case 'chat':
router.push('/(tabs)/chat');
break;
case 'profile':
router.push('/(tabs)/profile');
break;
default:
console.log('[Bug WebView] Unknown screen:', screen);
// Send error back to WebView
sendToWebView({ error: `Unknown screen: ${screen}` });
}
};
// Handle native feature requests
const handleNativeFeature = (feature: string) => {
console.log('[Bug WebView] Native feature requested:', feature);
switch (feature) {
case 'bluetooth':
// TODO: Implement Bluetooth scanning screen
sendToWebView({
status: 'not_implemented',
message: 'Bluetooth scanning will be implemented here'
});
break;
case 'camera':
// TODO: Implement camera
sendToWebView({
status: 'not_implemented',
message: 'Camera will be implemented here'
});
break;
default:
sendToWebView({ error: `Unknown feature: ${feature}` });
}
};
// Handle custom messages
const handleCustomMessage = (payload: any) => {
console.log('[Bug WebView] Custom message:', payload);
// Echo back with confirmation
sendToWebView({
status: 'received',
echo: payload,
processedAt: Date.now()
});
};
// Send message TO WebView
const sendToWebView = (data: object) => {
const script = `
window.dispatchEvent(new MessageEvent('message', {
data: '${JSON.stringify(data)}'
}));
true;
`;
webViewRef.current?.injectJavaScript(script);
};
return (
<SafeAreaView style={styles.container}>
<WebView
ref={webViewRef}
source={{ html: TEST_HTML }}
style={styles.webview}
onMessage={handleMessage}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
scalesPageToFit={true}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: AppColors.background,
},
webview: {
flex: 1,
},
});