Skip to content

Commit 9b2cf73

Browse files
authored
Merge pull request #665 from netzbegruenung/feat/mobile-group-content-and-hocuspocus-bearer
feat(mobile): open shared group content + fix Hocuspocus bearer auth
2 parents 249142a + 012990c commit 9b2cf73

5 files changed

Lines changed: 658 additions & 4 deletions

File tree

apps/mobile/app/(focused)/gruppen/[id]/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { SafeAreaView } from 'react-native-safe-area-context';
2020

2121
import { GroupAvatar } from '../../../../components/workplace/GroupAvatar';
22+
import { GroupContentSection } from '../../../../components/workplace/GroupContentSection';
2223
import {
2324
useDeleteGroup,
2425
useDeleteGroupAvatar,
@@ -354,6 +355,8 @@ export default function GroupDetailScreen() {
354355
</Pressable>
355356
) : null}
356357

358+
{id ? <GroupContentSection groupId={id} /> : null}
359+
357360
<Section title="Mitglieder" theme={theme}>
358361
<Pressable
359362
onPress={() => router.push(`/(focused)/gruppen/${id}/members`)}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { Ionicons } from '@expo/vector-icons';
2+
import { useLocalSearchParams, useRouter } from 'expo-router';
3+
import { StatusBar } from 'expo-status-bar';
4+
import { useCallback, useMemo, useRef, useState } from 'react';
5+
import { View, Text, Pressable, StyleSheet, ActivityIndicator, useColorScheme } from 'react-native';
6+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
7+
import { WebView } from 'react-native-webview';
8+
9+
import { secureStorage } from '../../services/storage';
10+
import { colors, lightTheme, darkTheme } from '../../theme';
11+
12+
const WEB_BASE = 'https://gruenerator.eu';
13+
14+
export default function WebViewerScreen() {
15+
const { path, title } = useLocalSearchParams<{ path?: string; title?: string }>();
16+
const router = useRouter();
17+
const colorScheme = useColorScheme();
18+
const theme = colorScheme === 'dark' ? darkTheme : lightTheme;
19+
const insets = useSafeAreaInsets();
20+
const webViewRef = useRef<WebView>(null);
21+
const [loading, setLoading] = useState(true);
22+
const [authToken, setAuthToken] = useState<string | null>(null);
23+
const [tokenReady, setTokenReady] = useState(false);
24+
25+
useMemo(() => {
26+
void secureStorage.getToken().then((t) => {
27+
setAuthToken(t);
28+
setTokenReady(true);
29+
});
30+
}, []);
31+
32+
const handleClose = useCallback(() => {
33+
router.back();
34+
}, [router]);
35+
36+
const normalizedPath = useMemo(() => {
37+
if (!path) return '/';
38+
const decoded = decodeURIComponent(path);
39+
if (decoded.startsWith('//') || !decoded.startsWith('/')) return '/';
40+
return decoded;
41+
}, [path]);
42+
43+
if (!path) {
44+
return (
45+
<View style={[styles.container, { backgroundColor: theme.background }]}>
46+
<Text style={{ color: theme.text }}>Kein Pfad angegeben.</Text>
47+
</View>
48+
);
49+
}
50+
51+
const targetUrl = `${WEB_BASE}/api/auth/v2/web-handoff?redirect=${encodeURIComponent(normalizedPath)}`;
52+
const headers = authToken ? { Authorization: `Bearer ${authToken}` } : undefined;
53+
54+
return (
55+
<View style={[styles.container, { backgroundColor: theme.background }]}>
56+
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
57+
<View
58+
style={[styles.header, { paddingTop: insets.top + 8, borderBottomColor: theme.border }]}
59+
>
60+
<Pressable onPress={handleClose} hitSlop={12} style={styles.closeButton}>
61+
<Ionicons name="close" size={24} color={theme.text} />
62+
</Pressable>
63+
<Text style={[styles.title, { color: theme.text }]} numberOfLines={1}>
64+
{title || 'Web'}
65+
</Text>
66+
<View style={styles.closeButton} />
67+
</View>
68+
69+
{!tokenReady ? (
70+
<View style={styles.loading}>
71+
<ActivityIndicator color={colors.primary[600]} />
72+
</View>
73+
) : (
74+
<>
75+
<WebView
76+
ref={webViewRef}
77+
source={{ uri: targetUrl, ...(headers ? { headers } : {}) }}
78+
sharedCookiesEnabled
79+
thirdPartyCookiesEnabled
80+
onLoadStart={() => setLoading(true)}
81+
onLoadEnd={() => setLoading(false)}
82+
style={styles.webview}
83+
allowsBackForwardNavigationGestures
84+
domStorageEnabled
85+
javaScriptEnabled
86+
/>
87+
{loading && (
88+
<View style={styles.loadingOverlay} pointerEvents="none">
89+
<ActivityIndicator color={colors.primary[600]} />
90+
</View>
91+
)}
92+
</>
93+
)}
94+
</View>
95+
);
96+
}
97+
98+
const styles = StyleSheet.create({
99+
container: { flex: 1 },
100+
header: {
101+
flexDirection: 'row',
102+
alignItems: 'center',
103+
justifyContent: 'space-between',
104+
paddingHorizontal: 16,
105+
paddingBottom: 8,
106+
borderBottomWidth: StyleSheet.hairlineWidth,
107+
},
108+
closeButton: {
109+
width: 40,
110+
height: 40,
111+
alignItems: 'center',
112+
justifyContent: 'center',
113+
},
114+
title: { fontSize: 16, fontWeight: '600' },
115+
webview: { flex: 1 },
116+
loading: { flex: 1, alignItems: 'center', justifyContent: 'center' },
117+
loadingOverlay: {
118+
position: 'absolute',
119+
top: 0,
120+
left: 0,
121+
right: 0,
122+
bottom: 0,
123+
alignItems: 'center',
124+
justifyContent: 'center',
125+
backgroundColor: 'rgba(255,255,255,0.01)',
126+
},
127+
});

0 commit comments

Comments
 (0)