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

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

RepoCard.tsxBlame164 lines · 1 contributor
c53cf00Claude1import React from 'react';
2import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3import { colors } from '../theme/colors';
4import { fontSizes, fontWeights } from '../theme/typography';
5import { type Repository } from '../api/client';
6
7interface Props {
8 repo: Repository;
9 ownerUsername?: string;
10 onPress: () => void;
11}
12
13const LANG_COLORS: Record<string, string> = {
14 TypeScript: '#3178c6',
15 JavaScript: '#f1e05a',
16 Python: '#3572A5',
17 Go: '#00ADD8',
18 Rust: '#dea584',
19 Ruby: '#701516',
20 Java: '#b07219',
21 'C++': '#f34b7d',
22 C: '#555555',
23 Swift: '#F05138',
24 Kotlin: '#A97BFF',
25 PHP: '#4F5D95',
26 'C#': '#239120',
27 Shell: '#89e051',
28};
29
30function timeAgo(dateStr: string): string {
31 const diff = Date.now() - new Date(dateStr).getTime();
32 const seconds = Math.floor(diff / 1000);
33 if (seconds < 60) return 'just now';
34 const minutes = Math.floor(seconds / 60);
35 if (minutes < 60) return `${minutes}m ago`;
36 const hours = Math.floor(minutes / 60);
37 if (hours < 24) return `${hours}h ago`;
38 const days = Math.floor(hours / 24);
39 if (days < 30) return `${days}d ago`;
40 const months = Math.floor(days / 30);
41 return `${months}mo ago`;
42}
43
44export function RepoCard({ repo, ownerUsername, onPress }: Props) {
45 const langColor = repo.language ? (LANG_COLORS[repo.language] ?? colors.textMuted) : colors.textMuted;
46
47 return (
48 <TouchableOpacity style={styles.card} onPress={onPress} activeOpacity={0.75}>
49 <View style={styles.header}>
50 <Text style={styles.name} numberOfLines={1}>
51 {ownerUsername ? `${ownerUsername}/` : ''}
52 <Text style={styles.repoName}>{repo.name}</Text>
53 </Text>
54 {repo.isPrivate && (
55 <View style={styles.privateBadge}>
56 <Text style={styles.privateBadgeText}>private</Text>
57 </View>
58 )}
59 </View>
60
61 {repo.description ? (
62 <Text style={styles.desc} numberOfLines={2}>
63 {repo.description}
64 </Text>
65 ) : null}
66
67 <View style={styles.meta}>
68 {repo.language && (
69 <View style={styles.langRow}>
70 <View style={[styles.langDot, { backgroundColor: langColor }]} />
71 <Text style={styles.metaText}>{repo.language}</Text>
72 </View>
73 )}
74 <View style={styles.metaItem}>
75 <Text style={styles.metaIcon}>★</Text>
76 <Text style={styles.metaText}>{repo.starCount}</Text>
77 </View>
78 <View style={styles.metaItem}>
79 <Text style={styles.metaIcon}>⑂</Text>
80 <Text style={styles.metaText}>{repo.forkCount}</Text>
81 </View>
82 <Text style={styles.metaTime}>{timeAgo(repo.updatedAt)}</Text>
83 </View>
84 </TouchableOpacity>
85 );
86}
87
88const styles = StyleSheet.create({
89 card: {
90 backgroundColor: colors.bgSurface,
91 borderRadius: 10,
92 padding: 14,
93 marginBottom: 10,
94 borderWidth: 1,
95 borderColor: colors.border,
96 },
97 header: {
98 flexDirection: 'row',
99 alignItems: 'center',
100 marginBottom: 6,
101 gap: 8,
102 },
103 name: {
104 flex: 1,
105 fontSize: fontSizes.base,
106 color: colors.textMuted,
107 fontWeight: fontWeights.regular,
108 },
109 repoName: {
110 color: colors.accent,
111 fontWeight: fontWeights.semibold,
112 },
113 privateBadge: {
114 backgroundColor: colors.accentDim,
115 borderRadius: 4,
116 paddingHorizontal: 6,
117 paddingVertical: 2,
118 },
119 privateBadgeText: {
120 color: colors.accent,
121 fontSize: fontSizes.xs,
122 fontWeight: fontWeights.medium,
123 },
124 desc: {
125 color: colors.textMuted,
126 fontSize: fontSizes.sm,
127 lineHeight: 18,
128 marginBottom: 10,
129 },
130 meta: {
131 flexDirection: 'row',
132 alignItems: 'center',
133 gap: 12,
134 flexWrap: 'wrap',
135 },
136 langRow: {
137 flexDirection: 'row',
138 alignItems: 'center',
139 gap: 4,
140 },
141 langDot: {
142 width: 10,
143 height: 10,
144 borderRadius: 5,
145 },
146 metaItem: {
147 flexDirection: 'row',
148 alignItems: 'center',
149 gap: 3,
150 },
151 metaIcon: {
152 color: colors.textMuted,
153 fontSize: fontSizes.xs,
154 },
155 metaText: {
156 color: colors.textMuted,
157 fontSize: fontSizes.xs,
158 },
159 metaTime: {
160 color: colors.textMuted,
161 fontSize: fontSizes.xs,
162 marginLeft: 'auto',
163 },
164});