- 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>
324 lines
8.2 KiB
TypeScript
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,
|
|
},
|
|
});
|