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

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

NotifRow.tsxBlame134 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 Notification } from '../hooks/useNotifications';
6
7interface Props {
8 notification: Notification;
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 return `${days}d ago`;
20}
21
22function typeIcon(type: string): string {
23 if (type.startsWith('issue')) return '●';
24 if (type.startsWith('pr')) return '⑂';
25 if (type === 'push') return '↑';
26 if (type === 'star') return '★';
27 return '◈';
28}
29
30function typeColor(type: string): string {
31 if (type.startsWith('issue')) return colors.green;
32 if (type.startsWith('pr')) return colors.accent;
33 if (type === 'push') return colors.blue;
34 if (type === 'star') return colors.yellow;
35 return colors.textMuted;
36}
37
38export function NotifRow({ notification, onPress }: Props) {
39 const icon = typeIcon(notification.type);
40 const iconColor = typeColor(notification.type);
41
42 return (
43 <TouchableOpacity style={[styles.row, !notification.read && styles.unread]} onPress={onPress} activeOpacity={0.75}>
44 <View style={styles.iconWrap}>
45 <Text style={[styles.icon, { color: iconColor }]}>{icon}</Text>
46 </View>
47
48 <View style={styles.content}>
49 <View style={styles.header}>
50 <Text style={styles.title} numberOfLines={1}>
51 {notification.title}
52 </Text>
53 {!notification.read && <View style={styles.unreadDot} />}
54 </View>
55 {notification.subtitle ? (
56 <Text style={styles.subtitle} numberOfLines={1}>{notification.subtitle}</Text>
57 ) : null}
58 <View style={styles.footer}>
59 {notification.repoOwner && notification.repoName && (
60 <Text style={styles.repo}>{notification.repoOwner}/{notification.repoName}</Text>
61 )}
62 <Text style={styles.time}>{timeAgo(notification.createdAt)}</Text>
63 </View>
64 </View>
65 </TouchableOpacity>
66 );
67}
68
69const styles = StyleSheet.create({
70 row: {
71 flexDirection: 'row',
72 alignItems: 'flex-start',
73 padding: 14,
74 borderBottomWidth: 1,
75 borderBottomColor: colors.border,
76 gap: 12,
77 },
78 unread: {
79 backgroundColor: colors.bgSecondary,
80 },
81 iconWrap: {
82 width: 28,
83 height: 28,
84 borderRadius: 14,
85 backgroundColor: colors.bgSurface,
86 alignItems: 'center',
87 justifyContent: 'center',
88 flexShrink: 0,
89 },
90 icon: {
91 fontSize: 14,
92 },
93 content: {
94 flex: 1,
95 gap: 3,
96 },
97 header: {
98 flexDirection: 'row',
99 alignItems: 'center',
100 gap: 6,
101 },
102 title: {
103 color: colors.text,
104 fontSize: fontSizes.base,
105 fontWeight: fontWeights.medium,
106 flex: 1,
107 },
108 unreadDot: {
109 width: 7,
110 height: 7,
111 borderRadius: 4,
112 backgroundColor: colors.accent,
113 flexShrink: 0,
114 },
115 subtitle: {
116 color: colors.textMuted,
117 fontSize: fontSizes.sm,
118 },
119 footer: {
120 flexDirection: 'row',
121 alignItems: 'center',
122 gap: 8,
123 marginTop: 2,
124 },
125 repo: {
126 color: colors.textMuted,
127 fontSize: fontSizes.xs,
128 flex: 1,
129 },
130 time: {
131 color: colors.textMuted,
132 fontSize: fontSizes.xs,
133 },
134});