- settings.tsx: labels on sign-out, feedback type chips, submit button - capture.tsx: labels on workspace selection chips - index.tsx: labels on workspace filter chips, note card pressables - search.tsx: labels on search input, result row pressables - note/[id].tsx: labels on edit, save, cancel buttons
220 lines
6.2 KiB
TypeScript
220 lines
6.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { Alert, Pressable, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
import { router } from 'expo-router';
|
|
import { useAuthStore, type AuthState } from '../../store/auth-store';
|
|
import { getFeedbackClient } from '../../lib/feedback-client';
|
|
import { APP_PLATFORM } from '../../lib/app-metadata';
|
|
import { colors } from '../../theme';
|
|
|
|
type FeedbackType = 'bug' | 'feature' | 'praise' | 'other';
|
|
|
|
export default function SettingsScreen() {
|
|
const email = useAuthStore((state: AuthState) => state.email);
|
|
const signOut = useAuthStore((state: AuthState) => state.signOut);
|
|
const [feedbackType, setFeedbackType] = useState<FeedbackType>('bug');
|
|
const [feedbackTitle, setFeedbackTitle] = useState('');
|
|
const [feedbackBody, setFeedbackBody] = useState('');
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
async function submitFeedback(): Promise<void> {
|
|
const title = feedbackTitle.trim();
|
|
if (!title || isSubmitting) {
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
await getFeedbackClient().submitWithScreenshot({
|
|
type: feedbackType,
|
|
title,
|
|
body: feedbackBody.trim() || undefined,
|
|
platform: APP_PLATFORM,
|
|
});
|
|
setFeedbackTitle('');
|
|
setFeedbackBody('');
|
|
Alert.alert('Feedback sent', 'Thanks for sharing your feedback.');
|
|
} catch {
|
|
Alert.alert('Feedback failed', 'Unable to submit feedback right now. Please try again later.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<ScrollView contentContainerStyle={styles.container}>
|
|
<Text style={styles.title}>Settings</Text>
|
|
<Text style={styles.subtitle}>Manage account and share feedback from mobile.</Text>
|
|
|
|
<View style={styles.card}>
|
|
<Text style={styles.cardTitle}>Account</Text>
|
|
<Text style={styles.metaLabel}>Signed in as</Text>
|
|
<Text style={styles.metaValue}>{email ?? 'Unknown account'}</Text>
|
|
<Pressable
|
|
accessibilityLabel="Sign out"
|
|
style={styles.secondaryButton}
|
|
onPress={() => {
|
|
signOut();
|
|
router.replace('/auth');
|
|
}}
|
|
>
|
|
<Text style={styles.secondaryButtonText}>Sign out</Text>
|
|
</Pressable>
|
|
</View>
|
|
|
|
<View style={styles.card}>
|
|
<Text style={styles.cardTitle}>Send feedback</Text>
|
|
<View style={styles.typeRow}>
|
|
{(['bug', 'feature', 'praise', 'other'] as const).map((type) => {
|
|
const isActive = type === feedbackType;
|
|
return (
|
|
<Pressable
|
|
accessibilityLabel={`Select ${type} feedback type`}
|
|
key={type}
|
|
style={[styles.typeChip, isActive ? styles.typeChipActive : null]}
|
|
onPress={() => setFeedbackType(type)}
|
|
>
|
|
<Text style={[styles.typeChipText, isActive ? styles.typeChipTextActive : null]}>{type}</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
<TextInput
|
|
value={feedbackTitle}
|
|
onChangeText={setFeedbackTitle}
|
|
placeholder="Feedback title"
|
|
placeholderTextColor={colors.textTertiary}
|
|
style={styles.input}
|
|
/>
|
|
<TextInput
|
|
value={feedbackBody}
|
|
onChangeText={setFeedbackBody}
|
|
placeholder="Details (optional)"
|
|
placeholderTextColor={colors.textTertiary}
|
|
style={[styles.input, styles.bodyInput]}
|
|
multiline
|
|
textAlignVertical="top"
|
|
/>
|
|
|
|
<Pressable
|
|
accessibilityLabel={isSubmitting ? 'Submitting feedback' : 'Submit feedback'}
|
|
style={[styles.primaryButton, feedbackTitle.trim().length === 0 || isSubmitting ? styles.buttonDisabled : null]}
|
|
disabled={feedbackTitle.trim().length === 0 || isSubmitting}
|
|
onPress={() => {
|
|
void submitFeedback();
|
|
}}
|
|
>
|
|
<Text style={styles.primaryButtonText}>{isSubmitting ? 'Submitting…' : 'Submit feedback'}</Text>
|
|
</Pressable>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
backgroundColor: colors.bgCanvas,
|
|
minHeight: '100%',
|
|
padding: 20,
|
|
gap: 14,
|
|
},
|
|
title: {
|
|
color: colors.textPrimary,
|
|
fontSize: 28,
|
|
fontWeight: '700',
|
|
},
|
|
subtitle: {
|
|
color: colors.textSecondary,
|
|
fontSize: 15,
|
|
lineHeight: 21,
|
|
},
|
|
card: {
|
|
borderRadius: 16,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderDefault,
|
|
backgroundColor: colors.surfaceCard,
|
|
padding: 16,
|
|
gap: 10,
|
|
},
|
|
cardTitle: {
|
|
color: colors.textPrimary,
|
|
fontSize: 17,
|
|
fontWeight: '700',
|
|
},
|
|
metaLabel: {
|
|
color: colors.textSecondary,
|
|
fontSize: 13,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.8,
|
|
},
|
|
metaValue: {
|
|
color: colors.textPrimary,
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
},
|
|
typeRow: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
},
|
|
typeChip: {
|
|
borderRadius: 999,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderDefault,
|
|
backgroundColor: colors.bgElevated,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 6,
|
|
},
|
|
typeChipActive: {
|
|
borderColor: colors.accentPrimary,
|
|
backgroundColor: colors.accentPrimary,
|
|
},
|
|
typeChipText: {
|
|
color: colors.textSecondary,
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
textTransform: 'capitalize',
|
|
},
|
|
typeChipTextActive: {
|
|
color: colors.textPrimary,
|
|
},
|
|
input: {
|
|
borderRadius: 12,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderDefault,
|
|
backgroundColor: colors.bgElevated,
|
|
color: colors.textPrimary,
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 10,
|
|
},
|
|
bodyInput: {
|
|
minHeight: 96,
|
|
},
|
|
primaryButton: {
|
|
borderRadius: 10,
|
|
backgroundColor: colors.accentPrimary,
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 10,
|
|
alignItems: 'center',
|
|
},
|
|
primaryButtonText: {
|
|
color: colors.textPrimary,
|
|
fontWeight: '700',
|
|
},
|
|
secondaryButton: {
|
|
borderRadius: 10,
|
|
borderWidth: 1,
|
|
borderColor: colors.borderDefault,
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 10,
|
|
alignItems: 'center',
|
|
},
|
|
secondaryButtonText: {
|
|
color: colors.textSecondary,
|
|
fontWeight: '600',
|
|
},
|
|
buttonDisabled: {
|
|
opacity: 0.6,
|
|
},
|
|
});
|