Update app and ignore local artifacts

This commit is contained in:
2026-09-04 12:59:28 +05:30
parent 8120ebb927
commit e6884a148b
56 changed files with 4419 additions and 370 deletions

142
app/account.tsx Normal file
View File

@@ -0,0 +1,142 @@
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import { useCallback, useEffect, useState } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import {
AppHeader,
AppText,
Button,
Card,
FormField,
IconButton,
InlineBanner,
Screen,
colors,
radius,
spacing,
} from '@/components/ui';
import { collegeApi, type Profile } from '@/lib/api';
export default function AccountScreen() {
const [profile, setProfile] = useState<Profile | null>(null);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState('');
const [editing, setEditing] = useState(false);
const [name, setName] = useState('');
const [college, setCollege] = useState('');
const [programme, setProgramme] = useState('');
const [semester, setSemester] = useState('');
const [saving, setSaving] = useState(false);
const [formError, setFormError] = useState('');
const load = useCallback(() => {
setLoading(true);
setLoadError('');
collegeApi.profile()
.then((result) => {
setProfile(result);
setName(result.name);
setCollege(result.college);
setProgramme(result.programme);
setSemester(result.semester);
})
.catch((error: Error) => setLoadError(error.message))
.finally(() => setLoading(false));
}, []);
useEffect(() => { load(); }, [load]);
const save = async () => {
if (!name.trim() || !college.trim()) {
setFormError('Your name and college are required.');
return;
}
setSaving(true);
setFormError('');
try {
const updated = await collegeApi.updateProfile({ name, college, programme, semester });
setProfile(updated);
setEditing(false);
} catch (error) {
setFormError(error instanceof Error ? error.message : 'Please try again.');
} finally {
setSaving(false);
}
};
return <Screen contentContainerStyle={styles.content}>
<AppHeader
compact
title="Account"
leading={<IconButton icon="chevron-back" label="Go back" onPress={() => router.back()} />}
trailing={profile ? <IconButton icon={editing ? 'close' : 'create-outline'} label={editing ? 'Cancel editing' : 'Edit profile'} onPress={() => { setEditing((value) => !value); setFormError(''); }} /> : undefined}
/>
{loading ? <View style={styles.loading}><ActivityIndicator color={colors.brand.cobalt} /><AppText variant="bodySmall" color={colors.neutral.textSecondary}>Loading profile</AppText></View> : null}
{loadError ? <InlineBanner title="Couldnt load your account" message={loadError} tone="danger" action={<Button label="Retry" variant="ghost" size="compact" fullWidth={false} onPress={load} />} style={styles.error} /> : null}
{profile ? <>
<View style={styles.profileHero}>
<View style={styles.avatar}>
<AppText variant="heading2">{profile.initials || '?'}</AppText>
<View style={styles.avatarDot} />
</View>
<AppText variant="heading2" style={styles.name}>{profile.name}</AppText>
<AppText variant="bodySmall" color={colors.neutral.textSecondary} style={styles.college}>{profile.college}</AppText>
</View>
{editing ? <Card style={styles.editCard}>
<AppText variant="heading3">Profile details</AppText>
{formError ? <InlineBanner title="Check your details" message={formError} tone="danger" style={styles.formError} /> : null}
<View style={styles.fields}>
<FormField label="Your name" value={name} onChangeText={setName} placeholder="Your name" autoCapitalize="words" autoComplete="name" />
<FormField label="College or university" value={college} onChangeText={setCollege} placeholder="College or university" autoCapitalize="words" />
<FormField label="Programme (optional)" value={programme} onChangeText={setProgramme} placeholder="e.g. B.Tech Computer Science" autoCapitalize="words" />
<FormField label="Semester (optional)" value={semester} onChangeText={setSemester} placeholder="e.g. Semester 3" autoCapitalize="words" />
</View>
<Button label="Save profile" loading={saving} haptic="success" onPress={save} />
</Card> : <>
<AppText variant="heading3" style={styles.sectionTitle}>College details</AppText>
<Card padding={0} style={styles.details}>
<Info icon="school-outline" label="College" value={profile.college || 'Not added'} />
<View style={styles.divider} />
<Info icon="book-outline" label="Programme" value={profile.programme || 'Not added'} />
<View style={styles.divider} />
<Info icon="calendar-outline" label="Current semester" value={profile.semester || 'Not added'} />
</Card>
</>}
</> : null}
</Screen>;
}
function Info({ icon, label, value }: { icon: keyof typeof Ionicons.glyphMap; label: string; value: string }) {
return <View style={styles.info}>
<View style={styles.infoIcon}><Ionicons name={icon} size={20} color={colors.brand.cobalt} /></View>
<View style={styles.infoCopy}>
<AppText variant="caption" color={colors.neutral.textMuted}>{label}</AppText>
<AppText variant="label" style={styles.infoValue}>{value}</AppText>
</View>
</View>;
}
const styles = StyleSheet.create({
content: { paddingBottom: spacing[9] },
loading: { minHeight: 220, alignItems: 'center', justifyContent: 'center', gap: spacing[3] },
error: { marginTop: spacing[6] },
profileHero: { marginTop: spacing[5], alignItems: 'center', borderRadius: radius.feature, backgroundColor: colors.brand.sky, padding: spacing[7] },
avatar: { width: 82, height: 82, borderRadius: 27, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.neutral.surface },
avatarDot: { position: 'absolute', right: 2, top: 2, width: 14, height: 14, borderRadius: 7, backgroundColor: colors.brand.coral, borderWidth: 3, borderColor: colors.neutral.surface },
name: { marginTop: spacing[4], textAlign: 'center' },
college: { marginTop: spacing[1], textAlign: 'center' },
editCard: { marginTop: spacing[4] },
formError: { marginTop: spacing[4] },
fields: { gap: spacing[4], marginVertical: spacing[5] },
sectionTitle: { marginTop: spacing[8], marginBottom: spacing[3] },
details: { overflow: 'hidden' },
info: { minHeight: 78, flexDirection: 'row', alignItems: 'center', gap: spacing[3], paddingHorizontal: spacing[4], paddingVertical: spacing[3] },
infoIcon: { width: 40, height: 40, borderRadius: radius.control, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.brand.cobaltSoft },
infoCopy: { flex: 1 },
infoValue: { marginTop: spacing[1] },
divider: { height: 1, marginLeft: 68, marginRight: spacing[4], backgroundColor: colors.neutral.divider },
});