frontend: drop redundant casts now that icons and categories are typed
Pedro Lucas Porcellis porcellis@eletrotupi.com · 12 days ago
9897f0373a757f09dbe54e9fae5842f71c99703d Browse filesParents
2bd9b67Changed files
12
Additions
+33
Deletions
-29
frontend/app/(tabs)/history.tsx
1 1
import { useState } from "react";
2 2
import { ScrollView, View } from "react-native";
3 3
import { ScreenLayout } from "@/components/ui/ScreenLayout";
4 4
import { Section, SectionHeader } from "@/components/ui/Sections";
5 5
import { FilterPills } from "@/components/ui/FilterPills";
15 15
import { EmptyState } from "@/components/ui/EmptyState";
16 16
17 17
function HistoryCardRenderer({ card }: { card: HistoryCard }) {
18 18
switch (card.category) {
19 19
case "mood":
20 -
return <MoodHistoryCard card={card as any} />;
20 +
return <MoodHistoryCard card={card} />;
21 21
case "sleep":
22 -
return <SleepRecordCard card={card as any} />;
22 +
return <SleepRecordCard card={card} />;
23 23
case "trigger":
24 -
return <TriggerHistoryCard card={card as any} />;
24 +
return <TriggerHistoryCard card={card} />;
25 25
case "care_action":
26 26
switch (card.subtype) {
27 27
case "medicine":
28 -
return <MedicineHistoryCard card={card as any} />;
28 +
return <MedicineHistoryCard card={card} />;
29 29
case "appointment":
30 -
return <AppointmentHistoryCard card={card as any} />;
30 +
return <AppointmentHistoryCard card={card} />;
31 31
case "activity":
32 -
return <ActivityHistoryCard card={card as any} />;
32 +
return <ActivityHistoryCard card={card} />;
33 33
default:
34 34
return null;
35 35
}
36 36
}
37 37
}
72 72
)}
73 73
</ScrollView>
74 74
</ScreenLayout>
75 75
);
76 76
}
frontend/app/care-actions/[id].tsx
1 1
import { useState, useEffect } from "react";
2 2
import {
3 3
StyleSheet,
4 4
View,
5 5
ScrollView,
60 60
MONTHLY: "1× por mês",
61 61
};
62 62
63 63
// header meta
64 64
type HeaderMeta = {
65 -
icon: string;
65 +
icon: keyof typeof MaterialIcons.glyphMap;
66 66
color: string;
67 67
iconBg: string;
68 68
title: string;
69 69
};
70 70
104 104
}
105 105
106 106
// DetailRow
107 107
// TODO: Might as well generalize this into a component
108 108
type DetailRowProps = {
109 -
icon: string;
109 +
icon: keyof typeof MaterialIcons.glyphMap;
110 110
label: string;
111 111
value: string;
112 112
};
113 113
114 114
function DetailRow({ icon, label, value }: DetailRowProps) {
115 115
return (
116 116
<Row style={styles.detailRow}>
117 117
<View style={styles.detailIconWrap}>
118 -
<MaterialIcons name={icon as any} size={18} color="#6366F1" />
118 +
<MaterialIcons name={icon} size={18} color="#6366F1" />
119 119
</View>
120 120
<Col gap={1}>
121 121
<Text style={styles.detailLabel}>{label}</Text>
122 122
<Text style={styles.detailValue}>{value}</Text>
123 123
</Col>
205 205
>
206 206
<Card style={{ padding: Spacing.cardGap }}>
207 207
<Row style={{ alignItems: "center" }}>
208 208
<View style={[styles.iconWrap, { backgroundColor: header.iconBg }]}>
209 209
<MaterialIcons
210 -
name={header.icon as any}
210 +
name={header.icon}
211 211
size={22}
212 212
color={header.color}
213 213
/>
214 214
</View>
215 215
<Col gap={2}>
315 315
styles.linkIconCircle,
316 316
{ backgroundColor: meta.iconBackground },
317 317
]}
318 318
>
319 319
<MaterialIcons
320 -
name={meta.icon as any}
320 +
name={meta.icon}
321 321
size={20}
322 322
color={meta.color}
323 323
/>
324 324
</View>
325 325
);
538 538
color: Colors.light.textSecondary,
539 539
fontStyle: "italic",
540 540
padding: 4,
541 541
},
542 542
});
frontend/app/entry/[id].tsx
1 1
import { useState, useEffect, use } from "react";
2 2
import {
3 3
StyleSheet,
4 4
View,
5 5
ScrollView,
222 222
<View style={styles.impactBar}>
223 223
<View
224 224
style={[
225 225
styles.impactFill,
226 226
{
227 -
width: `${(link.perceivedImpact / 5) * 100}%` as any,
227 +
width: `${(link.perceivedImpact / 5) * 100}%`,
228 228
backgroundColor: impactColor(link.perceivedImpact),
229 229
},
230 230
]}
231 231
/>
232 232
</View>
440 440
height: "100%",
441 441
borderRadius: 99,
442 442
},
443 443
444 444
});
frontend/components/history/ActivityHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import type { HistoryCard } from "@/lib/history/types";
18 18
});
19 19
20 20
return (
21 21
<Card
22 22
style={styles.card}
23 -
onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
23 +
onPress={() => router.push(`/care-actions/${card.raw.id}`)}
24 24
>
25 25
<View style={styles.row}>
26 26
<View style={[styles.iconWrap, { backgroundColor: meta.iconBackground }]}>
27 -
<MaterialIcons name={meta.icon as any} size={22} color={meta.color} />
27 +
<MaterialIcons name={meta.icon} size={22} color={meta.color} />
28 28
</View>
29 29
<View style={styles.body}>
30 30
<View style={styles.titleRow}>
31 31
<Text style={styles.title}>{card.title}</Text>
32 32
<Text style={styles.time}>{time}</Text>
60 60
},
61 61
title: { fontSize: 15, fontWeight: "600" },
62 62
time: { fontSize: 13, opacity: 0.45 },
63 63
summary: { fontSize: 13, opacity: 0.6, lineHeight: 18 },
64 64
});
frontend/components/history/AppointmentHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import { Badge } from "@/components/ui/Badge";
17 17
});
18 18
19 19
return (
20 20
<Card
21 21
style={styles.card}
22 -
onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
22 +
onPress={() => router.push(`/care-actions/${card.raw.id}`)}
23 23
>
24 24
<View style={styles.row}>
25 25
<View style={[styles.iconWrap, styles.iconBg]}>
26 26
<MaterialIcons name="psychology" size={22} color="#1D4ED8" />
27 27
</View>
36 36
</Text>
37 37
) : null}
38 38
{card.badge ? (
39 39
<Badge
40 40
label={card.badge.label}
41 -
variant={card.badge.variant as any}
41 +
variant={card.badge.variant}
42 42
style={styles.badge}
43 43
/>
44 44
) : null}
45 45
</View>
46 46
</View>
70 70
title: { fontSize: 15, fontWeight: "400" },
71 71
time: { fontSize: 13, opacity: 0.45 },
72 72
summary: { fontSize: 13, opacity: 0.6, lineHeight: 18, marginBottom: 6 },
73 73
badge: { alignSelf: "flex-start", marginTop: 4 },
74 74
});
frontend/components/history/MedicineHistoryCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from "@/components/ui/Text";
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import type { HistoryCard } from "@/lib/history/types";
16 16
});
17 17
18 18
return (
19 19
<Card
20 20
style={styles.card}
21 -
onPress={() => (router.push as any)(`/care-actions/${card.raw.id}`)}
21 +
onPress={() => router.push(`/care-actions/${card.raw.id}`)}
22 22
>
23 23
<View style={styles.row}>
24 24
<View style={[styles.iconWrap, styles.iconBg]}>
25 25
<MaterialIcons name="medication" size={22} color="#7C3AED" />
26 26
</View>
59 59
},
60 60
title: { fontSize: 15, fontWeight: "600" },
61 61
time: { fontSize: 13, opacity: 0.45 },
62 62
summary: { fontSize: 13, opacity: 0.6, lineHeight: 18 },
63 63
});
frontend/components/history/TriggerCard.tsx
1 1
import { View, StyleSheet } from "react-native";
2 2
import { Text } from '@/components/ui/Text';
3 3
import { MaterialIcons } from "@expo/vector-icons";
4 4
import { Card } from "@/components/ui/Cards";
5 5
import { Badge } from "@/components/ui/Badge";
34 34
{card.raw.comment}
35 35
</Text>
36 36
) : null}
37 37
<Badge
38 38
label={meta.label}
39 -
variant={meta.variant as any}
39 +
variant={meta.variant}
40 40
style={styles.badge}
41 41
/>
42 42
</View>
43 43
</View>
44 44
</Card>
68 68
title: { fontSize: 15, fontWeight: "600" },
69 69
time: { fontSize: 13, opacity: 0.45 },
70 70
summary: { fontSize: 13, opacity: 0.6, lineHeight: 18, marginBottom: 6 },
71 71
badge: { alignSelf: "flex-start", marginTop: 4 },
72 72
});
frontend/components/ui/CategoryChips.tsx
1 1
import {
2 2
TouchableOpacity,
3 3
View,
4 4
StyleSheet,
5 5
} from 'react-native';
34 34
key={option.value}
35 35
onPress={() => onChange(option.value)}
36 36
activeOpacity={0.7}
37 37
style={[
38 38
styles.chip,
39 -
{ width: `${100 / columns - 2}%` as any },
39 +
{ width: `${100 / columns - 2}%` },
40 40
isActive && styles.chipActive,
41 41
]}
42 42
>
43 43
<View style={styles.chipInner}>
44 44
<View style={styles.iconWrapper}>{option.icon}</View>
92 92
},
93 93
labelActive: {
94 94
fontWeight: '600',
95 95
},
96 96
});
frontend/constants/care-action-categories.ts
1 +
import { MaterialIcons } from "@expo/vector-icons";
1 2
import type { CareAction } from "@/lib/api/types";
2 3
3 4
export type CareActionMeta = {
4 -
icon: string;
5 +
icon: keyof typeof MaterialIcons.glyphMap;
5 6
color: string;
6 7
iconBackground: string;
7 8
label: string;
8 9
};
9 10
50 51
return getActivityCategory(ca.activity?.type);
51 52
default:
52 53
return { icon: 'star', color: '#6B7280', iconBackground: '#F3F4F6', label: 'Cuidado' };
53 54
}
54 55
}
frontend/context/AuthContext.tsx
1 1
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
2 2
import { apiClient, User } from "@/lib/api";
3 3
import { queryClient, storage } from "@/lib/queryClient";
4 4
5 5
interface AuthProviderProps {
25 25
resetPassword: (token: string, password: string) => Promise<void>;
26 26
activate: (code: string) => Promise<void>;
27 27
requestActivateCode: () => Promise<void>;
28 28
}
29 29
30 -
export function sanitizeUser(data: any): User {
30 +
export function sanitizeUser(data: User): User {
31 31
return {
32 32
id: data.id,
33 33
email: data.email,
34 34
firstName: data.firstName,
35 35
lastName: data.lastName,
168 168
[user, isLoading, isAuthenticated],
169 169
);
170 170
171 171
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
172 172
};
frontend/hooks/useNotificationObserver.ts
1 1
import { useEffect, useRef } from "react";
2 2
import * as Notifications from "expo-notifications";
3 -
import { useRouter, useRootNavigationState } from "expo-router";
3 +
import { useRouter, useRootNavigationState, type Href } from "expo-router";
4 4
5 5
Notifications.setNotificationHandler({
6 6
handleNotification: async () => ({
7 7
shouldPlaySound: true,
8 8
shouldSetBadge: false,
37 37
38 38
if (lastHandledId.current === id) return;
39 39
lastHandledId.current = id;
40 40
41 41
// XXX: Looks so dumb, actually
42 -
const { screen, ...params } = (data ?? {}) as Record<string, any>;
42 +
const { screen, ...params } = (data ?? {}) as {
43 +
screen?: string;
44 +
[key: string]: string | undefined;
45 +
};
43 46
44 47
if (!screen) return;
45 48
46 49
const payload = { body, title, ...params };
47 50
48 51
if (coldLaunch) {
49 52
router.replace({
50 -
pathname: screen as any,
53 +
pathname: screen,
51 54
params: payload,
52 -
});
55 +
} as Href);
53 56
} else {
54 57
router.push({
55 -
pathname: screen as any,
58 +
pathname: screen,
56 59
params: payload,
57 -
});
60 +
} as Href);
58 61
}
59 62
}
60 63
61 64
// Cold launch
62 65
Notifications.getLastNotificationResponseAsync().then((response) => {
77 80
isMounted = false;
78 81
subscription.remove();
79 82
};
80 83
}, [routerReady, router]);
81 84
}
frontend/hooks/useUserPreferences.queries.ts
1 1
import { useMutation } from "@tanstack/react-query";
2 -
import { apiClient, UserPreferencesPayload } from "@/lib/api";
2 +
import { apiClient, User, UserPreferencesPayload } from "@/lib/api";
3 3
4 -
export const usePatchUserMe = (onSuccess?: (user: any) => void) => {
4 +
export const usePatchUserMe = (onSuccess?: (user: User) => void) => {
5 5
return useMutation({
6 6
mutationFn: (payload: UserPreferencesPayload) => apiClient.patchUserMe(payload),
7 7
onSuccess: (data) => {
8 8
onSuccess?.(data.user);
9 9
},
10 10
});
11 11
};