CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
SettingsScreen.tsx
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| c53cf00 | 1 | import React, { useContext, useState } from 'react'; |
| 2 | import { | |
| 3 | View, | |
| 4 | Text, | |
| 5 | StyleSheet, | |
| 6 | TouchableOpacity, | |
| 7 | ScrollView, | |
| 8 | Alert, | |
| 9 | TextInput, | |
| 10 | } from 'react-native'; | |
| 11 | import { SafeAreaView } from 'react-native-safe-area-context'; | |
| 12 | import { colors } from '../theme/colors'; | |
| 13 | import { fontSizes, fontWeights } from '../theme/typography'; | |
| 9d7a803 | 14 | import { AuthContext } from '../navigation/AuthContext'; |
| c53cf00 | 15 | import { getBaseUrl, setBaseUrl } from '../api/client'; |
| 16 | import { saveHost } from '../store/auth'; | |
| 17 | ||
| 18 | const APP_VERSION = '1.0.0'; | |
| 19 | ||
| 20 | export function SettingsScreen() { | |
| 21 | const { user, logout } = useContext(AuthContext); | |
| 22 | const [host, setHostState] = useState(getBaseUrl()); | |
| 23 | const [editingHost, setEditingHost] = useState(false); | |
| 24 | ||
| 25 | async function handleLogout() { | |
| 26 | Alert.alert( | |
| 27 | 'Sign out', | |
| 28 | 'Are you sure you want to sign out?', | |
| 29 | [ | |
| 30 | { text: 'Cancel', style: 'cancel' }, | |
| 31 | { | |
| 32 | text: 'Sign out', | |
| 33 | style: 'destructive', | |
| 34 | onPress: async () => { | |
| 35 | await logout(); | |
| 36 | }, | |
| 37 | }, | |
| 38 | ] | |
| 39 | ); | |
| 40 | } | |
| 41 | ||
| 42 | async function handleSaveHost() { | |
| 43 | const trimmed = host.trim().replace(/\/$/, ''); | |
| 44 | if (!trimmed) return; | |
| 45 | setBaseUrl(trimmed); | |
| 46 | await saveHost(trimmed); | |
| 47 | setEditingHost(false); | |
| 48 | } | |
| 49 | ||
| 50 | return ( | |
| 51 | <SafeAreaView style={styles.safe} edges={['top']}> | |
| 52 | <ScrollView contentContainerStyle={styles.content}> | |
| 53 | {/* Title */} | |
| 54 | <Text style={styles.pageTitle}>Settings</Text> | |
| 55 | ||
| 56 | {/* User profile section */} | |
| 57 | <View style={styles.section}> | |
| 58 | <Text style={styles.sectionLabel}>Account</Text> | |
| 59 | <View style={styles.card}> | |
| 60 | <View style={styles.avatarRow}> | |
| 61 | <View style={styles.avatar}> | |
| 62 | <Text style={styles.avatarText}> | |
| 63 | {(user?.displayName || user?.username || '?')[0].toUpperCase()} | |
| 64 | </Text> | |
| 65 | </View> | |
| 66 | <View style={styles.userInfo}> | |
| 67 | <Text style={styles.displayName}>{user?.displayName || user?.username}</Text> | |
| 68 | <Text style={styles.username}>@{user?.username}</Text> | |
| 69 | {user?.email && <Text style={styles.email}>{user.email}</Text>} | |
| 70 | </View> | |
| 71 | </View> | |
| 72 | {user?.bio ? ( | |
| 73 | <Text style={styles.bio}>{user.bio}</Text> | |
| 74 | ) : null} | |
| 75 | </View> | |
| 76 | </View> | |
| 77 | ||
| 78 | {/* Host section */} | |
| 79 | <View style={styles.section}> | |
| 80 | <Text style={styles.sectionLabel}>Gluecron Host</Text> | |
| 81 | <View style={styles.card}> | |
| 82 | <View style={styles.hostRow}> | |
| 83 | <Text style={styles.hostLabel}>Server URL</Text> | |
| 84 | {!editingHost ? ( | |
| 85 | <View style={styles.hostValueRow}> | |
| 86 | <Text style={styles.hostValue} numberOfLines={1}>{host}</Text> | |
| 87 | <TouchableOpacity | |
| 88 | onPress={() => setEditingHost(true)} | |
| 89 | style={styles.editBtn} | |
| 90 | activeOpacity={0.75} | |
| 91 | > | |
| 92 | <Text style={styles.editBtnText}>Edit</Text> | |
| 93 | </TouchableOpacity> | |
| 94 | </View> | |
| 95 | ) : ( | |
| 96 | <View style={styles.hostEditRow}> | |
| 97 | <TextInput | |
| 98 | style={styles.hostInput} | |
| 99 | value={host} | |
| 100 | onChangeText={setHostState} | |
| 101 | autoCapitalize="none" | |
| 102 | autoCorrect={false} | |
| 103 | keyboardType="url" | |
| 104 | placeholder="https://gluecron.com" | |
| 105 | placeholderTextColor={colors.textMuted} | |
| 106 | returnKeyType="done" | |
| 107 | onSubmitEditing={handleSaveHost} | |
| 108 | /> | |
| 109 | <TouchableOpacity onPress={handleSaveHost} style={styles.saveBtn} activeOpacity={0.75}> | |
| 110 | <Text style={styles.saveBtnText}>Save</Text> | |
| 111 | </TouchableOpacity> | |
| 112 | <TouchableOpacity | |
| 113 | onPress={() => { setHostState(getBaseUrl()); setEditingHost(false); }} | |
| 114 | style={styles.cancelBtn} | |
| 115 | activeOpacity={0.75} | |
| 116 | > | |
| 117 | <Text style={styles.cancelBtnText}>Cancel</Text> | |
| 118 | </TouchableOpacity> | |
| 119 | </View> | |
| 120 | )} | |
| 121 | </View> | |
| 122 | <Text style={styles.hostHint}> | |
| 123 | Change this if you run a self-hosted Gluecron instance. | |
| 124 | </Text> | |
| 125 | </View> | |
| 126 | </View> | |
| 127 | ||
| 128 | {/* App info */} | |
| 129 | <View style={styles.section}> | |
| 130 | <Text style={styles.sectionLabel}>About</Text> | |
| 131 | <View style={styles.card}> | |
| 132 | <View style={styles.infoRow}> | |
| 133 | <Text style={styles.infoLabel}>App version</Text> | |
| 134 | <Text style={styles.infoValue}>{APP_VERSION}</Text> | |
| 135 | </View> | |
| 136 | <View style={styles.divider} /> | |
| 137 | <View style={styles.infoRow}> | |
| 138 | <Text style={styles.infoLabel}>Platform</Text> | |
| 139 | <Text style={styles.infoValue}>Gluecron Mobile</Text> | |
| 140 | </View> | |
| 141 | </View> | |
| 142 | </View> | |
| 143 | ||
| 144 | {/* Logout */} | |
| 145 | <View style={styles.section}> | |
| 146 | <TouchableOpacity style={styles.logoutBtn} onPress={handleLogout} activeOpacity={0.8}> | |
| 147 | <Text style={styles.logoutText}>Sign out</Text> | |
| 148 | </TouchableOpacity> | |
| 149 | </View> | |
| 150 | </ScrollView> | |
| 151 | </SafeAreaView> | |
| 152 | ); | |
| 153 | } | |
| 154 | ||
| 155 | const styles = StyleSheet.create({ | |
| 156 | safe: { flex: 1, backgroundColor: colors.bg }, | |
| 157 | content: { padding: 16, paddingBottom: 40 }, | |
| 158 | pageTitle: { | |
| 159 | color: colors.text, | |
| 160 | fontSize: fontSizes.xl, | |
| 161 | fontWeight: fontWeights.bold, | |
| 162 | marginBottom: 20, | |
| 163 | }, | |
| 164 | section: { marginBottom: 24 }, | |
| 165 | sectionLabel: { | |
| 166 | color: colors.textMuted, | |
| 167 | fontSize: fontSizes.xs, | |
| 168 | fontWeight: fontWeights.semibold, | |
| 169 | textTransform: 'uppercase', | |
| 170 | letterSpacing: 0.8, | |
| 171 | marginBottom: 8, | |
| 172 | }, | |
| 173 | card: { | |
| 174 | backgroundColor: colors.bgSurface, | |
| 175 | borderRadius: 12, | |
| 176 | padding: 16, | |
| 177 | borderWidth: 1, | |
| 178 | borderColor: colors.border, | |
| 179 | }, | |
| 180 | avatarRow: { | |
| 181 | flexDirection: 'row', | |
| 182 | alignItems: 'center', | |
| 183 | gap: 14, | |
| 184 | marginBottom: 8, | |
| 185 | }, | |
| 186 | avatar: { | |
| 187 | width: 52, | |
| 188 | height: 52, | |
| 189 | borderRadius: 26, | |
| 190 | backgroundColor: colors.accentDim, | |
| 191 | alignItems: 'center', | |
| 192 | justifyContent: 'center', | |
| 193 | borderWidth: 2, | |
| 194 | borderColor: colors.accent, | |
| 195 | }, | |
| 196 | avatarText: { | |
| 197 | color: colors.accent, | |
| 198 | fontSize: fontSizes.xl, | |
| 199 | fontWeight: fontWeights.bold, | |
| 200 | }, | |
| 201 | userInfo: { flex: 1 }, | |
| 202 | displayName: { | |
| 203 | color: colors.text, | |
| 204 | fontSize: fontSizes.md, | |
| 205 | fontWeight: fontWeights.semibold, | |
| 206 | marginBottom: 2, | |
| 207 | }, | |
| 208 | username: { color: colors.textMuted, fontSize: fontSizes.sm, marginBottom: 2 }, | |
| 209 | email: { color: colors.textMuted, fontSize: fontSizes.xs }, | |
| 210 | bio: { color: colors.textMuted, fontSize: fontSizes.sm, lineHeight: 18, marginTop: 4 }, | |
| 211 | hostRow: { marginBottom: 8 }, | |
| 212 | hostLabel: { color: colors.textMuted, fontSize: fontSizes.xs, fontWeight: fontWeights.medium, marginBottom: 6, textTransform: 'uppercase' }, | |
| 213 | hostValueRow: { flexDirection: 'row', alignItems: 'center', gap: 10 }, | |
| 214 | hostValue: { color: colors.text, fontSize: fontSizes.sm, flex: 1 }, | |
| 215 | editBtn: { paddingHorizontal: 10, paddingVertical: 5, backgroundColor: colors.bgSecondary, borderRadius: 6 }, | |
| 216 | editBtnText: { color: colors.accent, fontSize: fontSizes.xs, fontWeight: fontWeights.medium }, | |
| 217 | hostEditRow: { flexDirection: 'row', alignItems: 'center', gap: 8, flexWrap: 'wrap' }, | |
| 218 | hostInput: { | |
| 219 | flex: 1, | |
| 220 | backgroundColor: colors.bgSecondary, | |
| 221 | borderWidth: 1, | |
| 222 | borderColor: colors.border, | |
| 223 | borderRadius: 6, | |
| 224 | paddingHorizontal: 10, | |
| 225 | paddingVertical: 8, | |
| 226 | color: colors.text, | |
| 227 | fontSize: fontSizes.sm, | |
| 228 | minHeight: 36, | |
| 229 | }, | |
| 230 | saveBtn: { paddingHorizontal: 12, paddingVertical: 8, backgroundColor: colors.accent, borderRadius: 6 }, | |
| 231 | saveBtnText: { color: colors.white, fontSize: fontSizes.sm, fontWeight: fontWeights.medium }, | |
| 232 | cancelBtn: { paddingHorizontal: 10, paddingVertical: 8 }, | |
| 233 | cancelBtnText: { color: colors.textMuted, fontSize: fontSizes.sm }, | |
| 234 | hostHint: { color: colors.textMuted, fontSize: fontSizes.xs, lineHeight: 16 }, | |
| 235 | infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 4 }, | |
| 236 | infoLabel: { color: colors.textMuted, fontSize: fontSizes.sm }, | |
| 237 | infoValue: { color: colors.text, fontSize: fontSizes.sm, fontWeight: fontWeights.medium }, | |
| 238 | divider: { height: 1, backgroundColor: colors.border, marginVertical: 8 }, | |
| 239 | logoutBtn: { | |
| 240 | backgroundColor: colors.red + '22', | |
| 241 | borderWidth: 1, | |
| 242 | borderColor: colors.red + '55', | |
| 243 | borderRadius: 10, | |
| 244 | paddingVertical: 14, | |
| 245 | alignItems: 'center', | |
| 246 | }, | |
| 247 | logoutText: { color: colors.red, fontSize: fontSizes.base, fontWeight: fontWeights.semibold }, | |
| 248 | }); |