Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

DashboardScreen.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.

DashboardScreen.tsxBlame207 lines · 1 contributor
c53cf00Claude1import React, { useContext, useEffect, useState } from 'react';
2import {
3 View,
4 Text,
5 ScrollView,
6 StyleSheet,
7 TouchableOpacity,
8 RefreshControl,
9} from 'react-native';
10import { SafeAreaView } from 'react-native-safe-area-context';
11import { type NativeStackNavigationProp } from '@react-navigation/native-stack';
12import { colors } from '../theme/colors';
13import { fontSizes, fontWeights } from '../theme/typography';
9d7a803Claude14import { AuthContext } from '../navigation/AuthContext';
c53cf00Claude15import { useUserRepos } from '../hooks/useRepo';
16import { RepoCard } from '../components/RepoCard';
17import { LoadingSpinner } from '../components/LoadingSpinner';
9d7a803Claude18import { type MainStackParamList } from '../navigation/types';
c53cf00Claude19
20interface Props {
21 navigation: NativeStackNavigationProp<MainStackParamList>;
22}
23
24export function DashboardScreen({ navigation }: Props) {
25 const { user } = useContext(AuthContext);
26 const { repos, loading, error, refresh } = useUserRepos(user?.username ?? null);
27 const [refreshing, setRefreshing] = useState(false);
28
29 async function onRefresh() {
30 setRefreshing(true);
31 await refresh();
32 setRefreshing(false);
33 }
34
35 const recentRepos = repos.slice(0, 6);
36 const hour = new Date().getHours();
37 const greeting = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
38
39 return (
40 <SafeAreaView style={styles.safe} edges={['top']}>
41 <ScrollView
42 style={styles.scroll}
43 contentContainerStyle={styles.content}
44 refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor={colors.accent} />}
45 >
46 {/* Header */}
47 <View style={styles.header}>
48 <View style={styles.logoRow}>
49 <Text style={styles.logoIcon}>⬡</Text>
50 <Text style={styles.logoName}>
51 glue<Text style={styles.logoAccent}>cron</Text>
52 </Text>
53 </View>
54 <Text style={styles.greeting}>
55 {greeting}, <Text style={styles.username}>{user?.displayName || user?.username}</Text>
56 </Text>
57 <Text style={styles.subGreeting}>Here's what's happening in your repos.</Text>
58 </View>
59
60 {/* Stats row */}
61 <View style={styles.statsRow}>
62 <View style={styles.statCard}>
63 <Text style={styles.statNum}>{repos.length}</Text>
64 <Text style={styles.statLabel}>Repos</Text>
65 </View>
66 <View style={styles.statCard}>
67 <Text style={styles.statNum}>{repos.reduce((acc, r) => acc + r.starCount, 0)}</Text>
68 <Text style={styles.statLabel}>Stars</Text>
69 </View>
70 <View style={styles.statCard}>
71 <Text style={styles.statNum}>{repos.reduce((acc, r) => acc + r.issueCount, 0)}</Text>
72 <Text style={styles.statLabel}>Issues</Text>
73 </View>
74 </View>
75
76 {/* Recent Repos */}
77 <View style={styles.section}>
78 <View style={styles.sectionHeader}>
79 <Text style={styles.sectionTitle}>Recent repositories</Text>
80 <TouchableOpacity onPress={() => navigation.navigate('RepoList')} activeOpacity={0.7}>
81 <Text style={styles.seeAll}>See all</Text>
82 </TouchableOpacity>
83 </View>
84
85 {loading && !refreshing ? (
86 <LoadingSpinner size="small" />
87 ) : recentRepos.length === 0 ? (
88 <View style={styles.emptyWrap}>
89 <Text style={styles.emptyText}>No repositories yet</Text>
90 </View>
91 ) : (
92 recentRepos.map((repo) => (
93 <RepoCard
94 key={repo.id}
95 repo={repo}
96 onPress={() =>
97 navigation.navigate('RepoDetail', {
98 owner: user?.username ?? '',
99 repo: repo.name,
100 })
101 }
102 />
103 ))
104 )}
105 </View>
106 </ScrollView>
107 </SafeAreaView>
108 );
109}
110
111const styles = StyleSheet.create({
112 safe: {
113 flex: 1,
114 backgroundColor: colors.bg,
115 },
116 scroll: {
117 flex: 1,
118 },
119 content: {
120 padding: 16,
121 paddingBottom: 32,
122 },
123 header: {
124 marginBottom: 20,
125 },
126 logoRow: {
127 flexDirection: 'row',
128 alignItems: 'center',
129 gap: 6,
130 marginBottom: 16,
131 },
132 logoIcon: {
133 fontSize: 22,
134 color: colors.accent,
135 },
136 logoName: {
137 fontSize: fontSizes.md,
138 fontWeight: fontWeights.bold,
139 color: colors.text,
140 },
141 logoAccent: {
142 color: colors.accent,
143 },
144 greeting: {
145 fontSize: fontSizes.xl,
146 fontWeight: fontWeights.bold,
147 color: colors.text,
148 marginBottom: 4,
149 },
150 username: {
151 color: colors.accent,
152 },
153 subGreeting: {
154 color: colors.textMuted,
155 fontSize: fontSizes.sm,
156 },
157 statsRow: {
158 flexDirection: 'row',
159 gap: 10,
160 marginBottom: 24,
161 },
162 statCard: {
163 flex: 1,
164 backgroundColor: colors.bgSurface,
165 borderRadius: 10,
166 padding: 14,
167 alignItems: 'center',
168 borderWidth: 1,
169 borderColor: colors.border,
170 },
171 statNum: {
172 color: colors.accent,
173 fontSize: fontSizes.xl,
174 fontWeight: fontWeights.bold,
175 },
176 statLabel: {
177 color: colors.textMuted,
178 fontSize: fontSizes.xs,
179 marginTop: 2,
180 },
181 section: {
182 marginBottom: 24,
183 },
184 sectionHeader: {
185 flexDirection: 'row',
186 justifyContent: 'space-between',
187 alignItems: 'center',
188 marginBottom: 12,
189 },
190 sectionTitle: {
191 color: colors.text,
192 fontSize: fontSizes.md,
193 fontWeight: fontWeights.semibold,
194 },
195 seeAll: {
196 color: colors.accent,
197 fontSize: fontSizes.sm,
198 },
199 emptyWrap: {
200 padding: 20,
201 alignItems: 'center',
202 },
203 emptyText: {
204 color: colors.textMuted,
205 fontSize: fontSizes.sm,
206 },
207});