Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

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

IssueRow.tsxBlame127 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 Issue } from '../api/client';
6
7interface Props {
8 issue: Issue;
9 onPress: () => void;
10}
11
12function timeAgo(dateStr: string): string {
13 const diff = Date.now() - new Date(dateStr).getTime();
14 const minutes = Math.floor(diff / 60000);
15 if (minutes < 60) return `${minutes}m ago`;
16 const hours = Math.floor(minutes / 60);
17 if (hours < 24) return `${hours}h ago`;
18 const days = Math.floor(hours / 24);
19 if (days < 30) return `${days}d ago`;
20 return `${Math.floor(days / 30)}mo ago`;
21}
22
23export function IssueRow({ issue, onPress }: Props) {
24 const isOpen = issue.state === 'open';
25
26 return (
27 <TouchableOpacity style={styles.row} onPress={onPress} activeOpacity={0.75}>
28 <View style={[styles.dot, { backgroundColor: isOpen ? colors.green : colors.textMuted }]} />
29
30 <View style={styles.content}>
31 <Text style={styles.title} numberOfLines={2}>
32 {issue.title}
33 </Text>
34
35 <View style={styles.meta}>
36 <Text style={styles.number}>#{issue.number}</Text>
37 <Text style={styles.time}>opened {timeAgo(issue.createdAt)}</Text>
38 {typeof issue.commentCount === 'number' && issue.commentCount > 0 && (
39 <View style={styles.commentBadge}>
40 <Text style={styles.commentText}>◯ {issue.commentCount}</Text>
41 </View>
42 )}
43 </View>
44
45 {issue.labels && issue.labels.length > 0 && (
46 <View style={styles.labels}>
47 {issue.labels.slice(0, 3).map((label) => (
48 <View
49 key={label.id}
50 style={[styles.label, { backgroundColor: `#${label.color}22`, borderColor: `#${label.color}` }]}
51 >
52 <Text style={[styles.labelText, { color: `#${label.color}` }]}>{label.name}</Text>
53 </View>
54 ))}
55 </View>
56 )}
57 </View>
58 </TouchableOpacity>
59 );
60}
61
62const styles = StyleSheet.create({
63 row: {
64 flexDirection: 'row',
65 alignItems: 'flex-start',
66 padding: 14,
67 borderBottomWidth: 1,
68 borderBottomColor: colors.border,
69 gap: 12,
70 },
71 dot: {
72 width: 8,
73 height: 8,
74 borderRadius: 4,
75 marginTop: 5,
76 flexShrink: 0,
77 },
78 content: {
79 flex: 1,
80 gap: 4,
81 },
82 title: {
83 color: colors.text,
84 fontSize: fontSizes.base,
85 fontWeight: fontWeights.medium,
86 lineHeight: 20,
87 },
88 meta: {
89 flexDirection: 'row',
90 alignItems: 'center',
91 gap: 8,
92 flexWrap: 'wrap',
93 },
94 number: {
95 color: colors.textMuted,
96 fontSize: fontSizes.xs,
97 },
98 time: {
99 color: colors.textMuted,
100 fontSize: fontSizes.xs,
101 },
102 commentBadge: {
103 flexDirection: 'row',
104 alignItems: 'center',
105 gap: 3,
106 },
107 commentText: {
108 color: colors.textMuted,
109 fontSize: fontSizes.xs,
110 },
111 labels: {
112 flexDirection: 'row',
113 flexWrap: 'wrap',
114 gap: 4,
115 marginTop: 4,
116 },
117 label: {
118 borderRadius: 10,
119 paddingHorizontal: 8,
120 paddingVertical: 2,
121 borderWidth: 1,
122 },
123 labelText: {
124 fontSize: fontSizes.xs,
125 fontWeight: fontWeights.medium,
126 },
127});