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

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

PullRow.tsxBlame169 lines · 1 contributor
9989d86Claude1import React, { useCallback } from 'react';
2import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
3import { colors } from '../theme/colors';
4import type { PullRequest } from '../api/pulls';
5
6interface PullRowProps {
7 pr: PullRequest;
8 onPress: (pr: PullRequest) => void;
9}
10
11function formatRelative(dateStr: string): string {
12 const diff = Date.now() - new Date(dateStr).getTime();
13 const mins = Math.floor(diff / 60000);
14 if (mins < 1) return 'just now';
15 if (mins < 60) return `${mins}m ago`;
16 const hrs = Math.floor(mins / 60);
17 if (hrs < 24) return `${hrs}h ago`;
18 const days = Math.floor(hrs / 24);
19 if (days < 30) return `${days}d ago`;
20 const months = Math.floor(days / 30);
21 if (months < 12) return `${months}mo ago`;
22 return `${Math.floor(months / 12)}y ago`;
23}
24
25function stateColor(state: PullRequest['state']): string {
26 switch (state) {
27 case 'open': return colors.accent;
28 case 'merged': return colors.accentPurple;
29 case 'closed': return colors.accentRed;
30 }
31}
32
33function stateLabel(state: PullRequest['state']): string {
34 switch (state) {
35 case 'open': return 'Open';
36 case 'merged': return 'Merged';
37 case 'closed': return 'Closed';
38 }
39}
40
41export function PullRow({ pr, onPress }: PullRowProps): React.ReactElement {
42 const handlePress = useCallback(() => onPress(pr), [pr, onPress]);
43
44 return (
45 <TouchableOpacity
46 style={styles.row}
47 onPress={handlePress}
48 activeOpacity={0.75}
49 >
50 <View style={[styles.stateIndicator, { backgroundColor: stateColor(pr.state) }]} />
51
52 <View style={styles.content}>
53 <View style={styles.titleRow}>
54 <Text style={styles.title} numberOfLines={2}>
55 {pr.title}
56 </Text>
57 {pr.isDraft && (
58 <View style={styles.draftBadge}>
59 <Text style={styles.draftText}>Draft</Text>
60 </View>
61 )}
62 </View>
63
64 <View style={styles.branchInfo}>
65 <Text style={styles.branchText} numberOfLines={1}>
66 {pr.headBranch}
67 </Text>
68 <Text style={styles.branchArrow}>→</Text>
69 <Text style={styles.branchText} numberOfLines={1}>
70 {pr.baseBranch}
71 </Text>
72 </View>
73
74 <View style={styles.meta}>
75 <Text style={styles.metaText}>
76 #{pr.number} {stateLabel(pr.state)} {formatRelative(pr.createdAt)} by {pr.authorUsername}
77 </Text>
78 {pr.commentCount > 0 && (
79 <View style={styles.commentCount}>
80 <Text style={styles.metaIcon}>💬</Text>
81 <Text style={styles.metaText}>{pr.commentCount}</Text>
82 </View>
83 )}
84 </View>
85 </View>
86 </TouchableOpacity>
87 );
88}
89
90const styles = StyleSheet.create({
91 row: {
92 flexDirection: 'row',
93 backgroundColor: colors.bgSecondary,
94 borderBottomWidth: 1,
95 borderBottomColor: colors.border,
96 paddingVertical: 12,
97 paddingHorizontal: 16,
98 gap: 12,
99 },
100 stateIndicator: {
101 width: 14,
102 height: 14,
103 borderRadius: 7,
104 marginTop: 3,
105 flexShrink: 0,
106 },
107 content: {
108 flex: 1,
109 gap: 5,
110 },
111 titleRow: {
112 flexDirection: 'row',
113 alignItems: 'flex-start',
114 gap: 8,
115 },
116 title: {
117 fontSize: 14,
118 fontWeight: '500',
119 color: colors.text,
120 lineHeight: 20,
121 flex: 1,
122 },
123 draftBadge: {
124 paddingHorizontal: 7,
125 paddingVertical: 2,
126 borderRadius: 20,
127 backgroundColor: colors.bgTertiary,
128 borderWidth: 1,
129 borderColor: colors.border,
130 flexShrink: 0,
131 },
132 draftText: {
133 fontSize: 11,
134 color: colors.textMuted,
135 },
136 branchInfo: {
137 flexDirection: 'row',
138 alignItems: 'center',
139 gap: 6,
140 },
141 branchText: {
142 fontSize: 12,
143 fontFamily: 'monospace',
144 color: colors.textMuted,
145 maxWidth: 120,
146 },
147 branchArrow: {
148 fontSize: 12,
149 color: colors.textMuted,
150 },
151 meta: {
152 flexDirection: 'row',
153 alignItems: 'center',
154 justifyContent: 'space-between',
155 },
156 metaText: {
157 fontSize: 12,
158 color: colors.textMuted,
159 flex: 1,
160 },
161 commentCount: {
162 flexDirection: 'row',
163 alignItems: 'center',
164 gap: 3,
165 },
166 metaIcon: {
167 fontSize: 11,
168 },
169});