Skip to content

Commit 5534b8e

Browse files
dfallingclaude
andauthored
Replace bottom logout bar with account avatar + bottom sheet (#9)
Top-right avatar opens a bottom sheet with the signed-in email and a single Log out action. Drops the map overlays' bottom-bar clearance now that nothing sits there. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 49d0e2d commit 5534b8e

2 files changed

Lines changed: 116 additions & 46 deletions

File tree

App.tsx

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
*/
66

77
import {ApolloProvider} from '@apollo/client';
8-
import {useEffect} from 'react';
8+
import {useEffect, useState} from 'react';
99
import {
1010
ActivityIndicator,
11+
Modal,
1112
Pressable,
1213
StatusBar,
1314
StyleSheet,
@@ -22,7 +23,12 @@ import {
2223
import {apolloClient, logout} from './src/auth/authClient';
2324
import {useDeepLinkListener} from './src/auth/deepLinks';
2425
import {LoginScreen} from './src/auth/LoginScreen';
25-
import {tokenStore, useAuth, useAuthHydrated} from './src/auth/tokenStore';
26+
import {
27+
type AuthUser,
28+
tokenStore,
29+
useAuth,
30+
useAuthHydrated,
31+
} from './src/auth/tokenStore';
2632
import {MapScreen} from './src/map/MapScreen';
2733

2834
function App() {
@@ -41,7 +47,6 @@ function App() {
4147
function AppContent() {
4248
const hydrated = useAuthHydrated();
4349
const auth = useAuth();
44-
const safeAreaInsets = useSafeAreaInsets();
4550

4651
useDeepLinkListener();
4752

@@ -64,25 +69,63 @@ function AppContent() {
6469
return (
6570
<View style={styles.container}>
6671
<MapScreen />
67-
<View
68-
style={[styles.logoutBar, {paddingBottom: safeAreaInsets.bottom + 12}]}>
69-
<Text style={styles.signedInAs}>Signed in as {auth.user.email}</Text>
70-
<Pressable
71-
accessibilityRole="button"
72-
onPress={() => {
73-
logout();
74-
}}
75-
style={({pressed}) => [
76-
styles.logoutButton,
77-
pressed && styles.logoutPressed,
78-
]}>
79-
<Text style={styles.logoutText}>Log out</Text>
80-
</Pressable>
81-
</View>
72+
<AccountMenu user={auth.user} />
8273
</View>
8374
);
8475
}
8576

77+
function AccountMenu({user}: {user: AuthUser}) {
78+
const safeAreaInsets = useSafeAreaInsets();
79+
const [open, setOpen] = useState(false);
80+
81+
const initial = user.email.trim().charAt(0).toUpperCase() || '?';
82+
83+
return (
84+
<>
85+
<Pressable
86+
accessibilityLabel="Account menu"
87+
accessibilityRole="button"
88+
onPress={() => setOpen(true)}
89+
style={({pressed}) => [
90+
styles.avatarButton,
91+
{top: safeAreaInsets.top + 12},
92+
pressed && styles.avatarPressed,
93+
]}>
94+
<Text style={styles.avatarText}>{initial}</Text>
95+
</Pressable>
96+
<Modal
97+
animationType="slide"
98+
transparent
99+
visible={open}
100+
onRequestClose={() => setOpen(false)}>
101+
<Pressable style={styles.sheetBackdrop} onPress={() => setOpen(false)}>
102+
<View
103+
style={[styles.sheet, {paddingBottom: safeAreaInsets.bottom + 12}]}
104+
// Stop taps inside the sheet from dismissing it via the backdrop.
105+
onStartShouldSetResponder={() => true}>
106+
<View style={styles.sheetHandle} />
107+
<Text style={styles.sheetEmail} numberOfLines={1}>
108+
{user.email}
109+
</Text>
110+
<Pressable
111+
accessibilityRole="button"
112+
onPress={() => {
113+
setOpen(false);
114+
logout();
115+
}}
116+
style={({pressed}) => [
117+
styles.sheetItem,
118+
pressed && styles.sheetItemPressed,
119+
]}>
120+
<Text style={styles.sheetItemText}>Log out</Text>
121+
</Pressable>
122+
</View>
123+
</Pressable>
124+
</Modal>
125+
</>
126+
);
127+
}
128+
86129
const styles = StyleSheet.create({
87130
container: {
88131
flex: 1,
@@ -91,35 +134,63 @@ const styles = StyleSheet.create({
91134
alignItems: 'center',
92135
justifyContent: 'center',
93136
},
94-
logoutBar: {
137+
avatarButton: {
95138
position: 'absolute',
96-
left: 0,
97-
right: 0,
98-
bottom: 0,
99-
paddingHorizontal: 16,
100-
paddingTop: 12,
101-
backgroundColor: 'rgba(255,255,255,0.95)',
102-
borderTopWidth: StyleSheet.hairlineWidth,
103-
borderTopColor: '#ccc',
104-
flexDirection: 'row',
139+
right: 16,
140+
width: 40,
141+
height: 40,
142+
borderRadius: 20,
143+
backgroundColor: '#1d6fe0',
105144
alignItems: 'center',
106-
justifyContent: 'space-between',
145+
justifyContent: 'center',
146+
shadowColor: '#000',
147+
shadowOpacity: 0.2,
148+
shadowRadius: 4,
149+
shadowOffset: {width: 0, height: 2},
150+
elevation: 4,
107151
},
108-
signedInAs: {
109-
fontSize: 12,
110-
color: '#444',
152+
avatarPressed: {opacity: 0.8},
153+
avatarText: {
154+
color: '#ffffff',
155+
fontSize: 16,
156+
fontWeight: '600',
157+
},
158+
sheetBackdrop: {
111159
flex: 1,
112-
marginRight: 12,
160+
backgroundColor: 'rgba(0,0,0,0.35)',
161+
justifyContent: 'flex-end',
162+
},
163+
sheet: {
164+
backgroundColor: '#ffffff',
165+
borderTopLeftRadius: 16,
166+
borderTopRightRadius: 16,
167+
paddingTop: 8,
168+
paddingHorizontal: 8,
113169
},
114-
logoutButton: {
170+
sheetHandle: {
171+
alignSelf: 'center',
172+
width: 36,
173+
height: 4,
174+
borderRadius: 2,
175+
backgroundColor: '#d0d0d0',
176+
marginBottom: 12,
177+
},
178+
sheetEmail: {
179+
fontSize: 12,
180+
color: '#666',
115181
paddingHorizontal: 12,
116-
paddingVertical: 8,
117-
borderRadius: 6,
118-
backgroundColor: '#eee',
182+
paddingBottom: 8,
183+
},
184+
sheetItem: {
185+
paddingHorizontal: 12,
186+
paddingVertical: 14,
187+
borderRadius: 8,
188+
},
189+
sheetItemPressed: {
190+
backgroundColor: '#f2f2f2',
119191
},
120-
logoutPressed: {opacity: 0.7},
121-
logoutText: {
122-
fontSize: 14,
192+
sheetItemText: {
193+
fontSize: 16,
123194
color: '#222',
124195
},
125196
});

src/map/MapScreen.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ const DEFAULT_INITIAL_VIEW = {center: [0, 20] as [number, number], zoom: 1};
2929
// Show the recenter button once the viewport center drifts more than this
3030
// fraction of the visible span away from the user in either axis.
3131
const OFF_CENTER_THRESHOLD = 0.2;
32-
// Vertical clearance above the device safe area so the recenter button sits
33-
// above the app's bottom "signed in as…" bar in App.tsx. Keep in sync if that
34-
// bar's height changes.
35-
const BOTTOM_BAR_CLEARANCE = 64;
32+
// Margin above the device safe area for bottom-anchored overlays (recenter
33+
// button, preview card).
34+
const BOTTOM_MARGIN = 16;
3635

3736
export function MapScreen() {
3837
const cameraRef = useRef<CameraRef>(null);
@@ -233,15 +232,15 @@ export function MapScreen() {
233232
onPress={flyToUser}
234233
style={[
235234
styles.recenterButton,
236-
{bottom: safeAreaInsets.bottom + BOTTOM_BAR_CLEARANCE},
235+
{bottom: safeAreaInsets.bottom + BOTTOM_MARGIN},
237236
]}>
238237
<Text style={styles.recenterIcon}></Text>
239238
</TouchableOpacity>
240239
) : null}
241240
{selectedElementId ? (
242241
<ElementPreviewCard
243242
elementId={selectedElementId}
244-
bottomOffset={safeAreaInsets.bottom + BOTTOM_BAR_CLEARANCE}
243+
bottomOffset={safeAreaInsets.bottom + BOTTOM_MARGIN}
245244
onClose={() => setSelectedElementId(null)}
246245
onExpand={() => setDetailExpanded(true)}
247246
/>

0 commit comments

Comments
 (0)