frontend: introduce triggers hooks + dispatch save to the API

Pedro Lucas Porcellis porcellis@eletrotupi.com 14 days ago 4ef759a7c84b0f1067b873dcf561cc752569a817
Parents: cd79c89
6 file(s) changed
  • frontend/app/triggers/new.tsx +51 -54
  • frontend/constants/triggers.ts +7 -1
  • frontend/hooks/index.ts +8 -10
  • frontend/hooks/useTrigger.queries.ts +120 -0
  • frontend/lib/api/client.ts +97 -53
  • frontend/lib/api/types.ts +31 -14
frontend/app/triggers/new.tsx
@@ -3,75 +3,73 @@ View,
3 3 Text,
4 4 StyleSheet,
5 5 KeyboardAvoidingView,
6 - ScrollView
7 - } from 'react-native';
8 - import { useEffect, useState } from 'react';
9 - import { router } from 'expo-router';
6 + ScrollView,
7 + } from "react-native";
8 + import { useEffect, useState } from "react";
9 + import { router } from "expo-router";
10 10
11 - import { Card } from '@/components/ui/Cards';
12 - import { Input, TextArea } from '@/components/ui/Input';
13 - import { Button } from '@/components/ui/Button';
14 - import { Section, SectionHeader } from '@/components/ui/Sections';
15 - import { Spacing, Typography, Colors, BorderRadius } from '@/constants/theme';
16 - import { Ionicons, MaterialIcons } from '@expo/vector-icons';
17 - import { useCreateSleepRecord } from '@/hooks';
18 - import { CategoryChips, CategoryOption } from '@/components/ui/CategoryChips';
19 - import { TriggerCategory } from '@/constants/triggers';
11 + import { Card } from "@/components/ui/Cards";
12 + import { Input, TextArea } from "@/components/ui/Input";
13 + import { Button } from "@/components/ui/Button";
14 + import { Section, SectionHeader } from "@/components/ui/Sections";
15 + import { Spacing, Typography, Colors, BorderRadius } from "@/constants/theme";
16 + import { Ionicons, MaterialIcons } from "@expo/vector-icons";
17 + import { useCreateTrigger } from "@/hooks";
18 + import { CategoryChips, CategoryOption } from "@/components/ui/CategoryChips";
19 + import { TriggerCategory } from "@/constants/triggers";
20 20
21 21 const TRIGGER_CATEGORIES: CategoryOption<TriggerCategory>[] = [
22 22 {
23 - label: 'Trabalho',
24 - value: 'work',
25 - icon: <Ionicons size={16} color="#60A5FA" name="briefcase" />
23 + label: "Trabalho",
24 + value: "WORK",
25 + icon: <Ionicons size={16} color="#60A5FA" name="briefcase" />,
26 26 },
27 27 {
28 - label: 'Social',
29 - value: 'social',
30 - icon: <MaterialIcons size={16} color="#AF52DE" name="diversity-1" />
28 + label: "Social",
29 + value: "SOCIAL",
30 + icon: <MaterialIcons size={16} color="#AF52DE" name="diversity-1" />,
31 31 },
32 32 {
33 - label:
34 - 'Saúde',
35 - value: 'health',
36 - icon: <MaterialIcons size={16} color="#34C759" name="healing" />
33 + label: "Saúde",
34 + value: "HEALTH",
35 + icon: <MaterialIcons size={16} color="#34C759" name="healing" />,
37 36 },
38 37 {
39 - label: 'Família',
40 - value: 'family',
41 - icon: <MaterialIcons size={16} color="#FF9500" name="family-restroom" />
38 + label: "Família",
39 + value: "FAMILY",
40 + icon: <MaterialIcons size={16} color="#FF9500" name="family-restroom" />,
42 41 },
43 42 {
44 - label: 'Outros',
45 - value: 'other',
46 - icon: <Ionicons size={16} color="#8E8E93" name="ellipsis-horizontal" />
43 + label: "Outros",
44 + value: "OTHER",
45 + icon: <Ionicons size={16} color="#8E8E93" name="ellipsis-horizontal" />,
47 46 },
48 47 ];
49 48
50 49 export default function NewTrigger() {
51 - const [moment, setMoment] = useState(new Date())
52 - const [comment, setComment] = useState();
53 - const [category, setCategory] = useState('work');
50 + const [moment, setMoment] = useState(new Date());
51 + const [comment, setComment] = useState("");
52 + const [category, setCategory] = useState("WORK");
53 + const createTrigger = useCreateTrigger();
54 54
55 55 const save = async () => {
56 56 const data = {
57 57 moment,
58 58 comment,
59 + category,
59 60 };
60 61
61 - console.log("Trigger data: ", data)
62 - //await createSleepRecord.mutateAsync(data)
63 - router.replace("/")
62 + await createTrigger.mutateAsync(data);
63 + router.replace("/(tabs)/actions");
64 64 };
65 65
66 66 return (
67 - <KeyboardAvoidingView
68 - behavior='height'
69 - style={styles.keyboardView}>
67 + <KeyboardAvoidingView behavior="height" style={styles.keyboardView}>
70 68 <ScrollView scrollEventThrottle={16}>
71 69 <View style={styles.container}>
72 70 <View style={styles.header}>
73 71 <View style={styles.innerHeaderWrapper}>
74 - <View style={[styles.headerIcon, { backgroundColor: '#FFF7ED' }]}>
72 + <View style={[styles.headerIcon, { backgroundColor: "#FFF7ED" }]}>
75 73 <Ionicons name="flash-outline" size={20} color="#EA580C" />
76 74 </View>
77 75
@@ -84,7 +82,8 @@ </View>
84 82 </View>
85 83
86 84 <Text style={styles.description}>
87 - Identificar gatilhos ajuda a entender melhor seus padrões emocionais
85 + Identificar gatilhos ajuda a entender melhor seus padrões
86 + emocionais
88 87 </Text>
89 88 </View>
90 89
@@ -93,7 +92,8 @@ <SectionHeader title="Selecionar uma categoria" />
93 92 <CategoryChips
94 93 options={TRIGGER_CATEGORIES}
95 94 active={category}
96 - onChange={setCategory} />
95 + onChange={setCategory}
96 + />
97 97 </Section>
98 98
99 99 <Section>
@@ -102,14 +102,12 @@
102 102 <Card style={styles.mainCard}>
103 103 <TextArea
104 104 label="Comentários sobre"
105 - type="text"
106 105 variant="darkGhost"
107 106 onChangeText={(val) => setComment(val)}
108 107 value={comment}
109 108 minRows={5}
110 109 maxRows={10}
111 110 placeholder="Descreva o que gerou esse sentimento"
112 - style={styles.notes}
113 111 />
114 112 </Card>
115 113 </Section>
@@ -126,14 +124,13 @@ container: {
126 124 gap: 24,
127 125 paddingHorizontal: Spacing.containerPadding,
128 126 paddingVertical: Spacing.sectionGap,
129 - marginBottom: 50
127 + marginBottom: 50,
130 128 },
131 129 mainCard: {
132 - padding: Spacing.cardGap
130 + padding: Spacing.cardGap,
133 131 },
134 132 // Header
135 - header: {
136 - },
133 + header: {},
137 134 headerIcon: {
138 135 paddingVertical: 15,
139 136 paddingHorizontal: 14,
@@ -142,25 +139,25 @@ borderRadius: BorderRadius.md,
142 139 },
143 140 innerHeaderWrapper: {
144 141 flex: 1,
145 - flexDirection: 'row',
146 - alignItems: 'center'
142 + flexDirection: "row",
143 + alignItems: "center",
147 144 },
148 145 headerTitleSubtitle: {
149 - marginLeft: 10
146 + marginLeft: 10,
150 147 },
151 148 headerTitle: {
152 - ...Typography.headlineMd
149 + ...Typography.headlineMd,
153 150 },
154 151 headerSubtitle: {
155 152 ...Typography.bodyMd,
156 - color: Colors.light.textSecondary
153 + color: Colors.light.textSecondary,
157 154 },
158 155 description: {
159 156 ...Typography.bodyMd,
160 157 color: Colors.light.textSecondary,
161 - marginTop: 20
158 + marginTop: 20,
162 159 },
163 160 keyboardView: {
164 161 flex: 1,
165 162 },
166 - })
163 + });
frontend/constants/triggers.ts
@@ -1,1 +1,7 @@
1 - export type TriggerCategory = 'work' | 'social' | 'health' | 'family' | 'other';
1 + export type TriggerCategory =
2 + | "WORK"
3 + | "SOCIAL"
4 + | "HEALTH"
5 + | "PHYSICAL"
6 + | "FAMILY"
7 + | "OTHER";
frontend/hooks/index.ts
@@ -1,11 +1,7 @@
1 1 // Legacy shit from react native
2 - export {
3 - useThemeColor
4 - } from '@/hooks/use-theme-color';
2 + export { useThemeColor } from "@/hooks/use-theme-color";
5 3
6 - export {
7 - useColorScheme
8 - } from '@/hooks/use-color-scheme';
4 + export { useColorScheme } from "@/hooks/use-color-scheme";
9 5
10 6 // Custom stuff
11 7 export {
@@ -14,10 +10,12 @@ useMoodEntries,
14 10 useMoodEntriesInfinite,
15 11 useMoodEntry,
16 12 useCreateMoodEntry,
17 - useDeleteMoodEntry
18 - } from '@/hooks/useMoodEntries.queries';
13 + useDeleteMoodEntry,
14 + } from "@/hooks/useMoodEntries.queries";
19 15
20 16 export {
21 17 useSleepRecords,
22 - useCreateSleepRecord
23 - } from '@/hooks/useSleepRecord.queries';
18 + useCreateSleepRecord,
19 + } from "@/hooks/useSleepRecord.queries";
20 +
21 + export { useTriggers, useCreateTrigger } from "@/hooks/useTrigger.queries";
frontend/hooks/useTrigger.queries.ts
@@ -0,0 +1,120 @@
1 + import {
2 + useQuery,
3 + useMutation,
4 + useQueryClient,
5 + useInfiniteQuery,
6 + } from "@tanstack/react-query";
7 +
8 + import { apiClient, Trigger, CreateTriggerPayload } from "@/lib/api";
9 +
10 + export const triggerKeys = {
11 + all: () => ["triggers"] as const,
12 + lists: () => [...triggerKeys.all(), "list"] as const,
13 + list: (filters?: TriggerFilters) =>
14 + [...triggerKeys.lists(), filters] as const,
15 + detail: (id: string) => [...triggerKeys.all(), "detail", id] as const,
16 + };
17 +
18 + interface TriggerFilters {
19 + from?: string; // ISO date
20 + to?: string; // ISO date
21 + limit?: number;
22 + }
23 +
24 + // Queries
25 +
26 + // Fetch a paginated list of mood entries.
27 + // On cold start, returns cached data instantly then revalidates.
28 + export const useTriggers = (filters?: TriggerFilters) => {
29 + return useQuery({
30 + queryKey: triggerKeys.list(filters),
31 + queryFn: () => apiClient.getTriggers(filters),
32 + });
33 + };
34 +
35 + // Single entry detail
36 + export const useTrigger = (id: string) => {
37 + return useQuery({
38 + queryKey: triggerKeys.detail(id),
39 + queryFn: () => apiClient.getTrigger(id),
40 + enabled: !!id,
41 + });
42 + };
43 +
44 + // MUTATIONS
45 +
46 + // Create a new mood entry
47 + // Optimistically adds to cache, rolls back on failure
48 + export const useCreateTrigger = () => {
49 + const queryClient = useQueryClient();
50 +
51 + return useMutation({
52 + mutationFn: (payload: CreateTriggerPayload) =>
53 + apiClient.createTrigger(payload),
54 +
55 + // Optimistic update so that the UI reflects the new entry immediately
56 + onMutate: async (payload) => {
57 + // Cancel any in-flight refetches so they don't overwrite our optimistic update
58 + await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
59 +
60 + // Snapshot current cache so we can roll back
61 + const previous = queryClient.getQueryData(triggerKeys.list());
62 +
63 + // Inject a temporary optimistic record
64 + // TODO: might as well create a shared attrs that we're keeping it
65 + queryClient.setQueryData(triggerKeys.list(), (old: Trigger[] = []) => [
66 + {
67 + ...payload,
68 + id: `temp-${Date.now()}`,
69 + createdAt: new Date().toISOString(),
70 + _optimistic: true,
71 + },
72 + ...old,
73 + ]);
74 +
75 + return { previous };
76 + },
77 +
78 + // On error, roll back to the snapshot
79 + onError: (_err, _payload, context) => {
80 + if (context?.previous) {
81 + queryClient.setQueryData(triggerKeys.list(), context.previous);
82 + }
83 + },
84 +
85 + // On success, replace optimistic record with real one from server
86 + onSuccess: () => {
87 + queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
88 + },
89 + });
90 + };
91 +
92 + // Delete a mood entry with optimistic removal
93 + export const useDeleteTrigger = () => {
94 + const queryClient = useQueryClient();
95 +
96 + return useMutation({
97 + mutationFn: (id: string) => apiClient.deleteTrigger(id),
98 +
99 + onMutate: async (id) => {
100 + await queryClient.cancelQueries({ queryKey: triggerKeys.lists() });
101 + const previous = queryClient.getQueryData(triggerKeys.list());
102 +
103 + queryClient.setQueryData(triggerKeys.list(), (old: Trigger[] = []) =>
104 + old.filter((e) => e.id !== id),
105 + );
106 +
107 + return { previous };
108 + },
109 +
110 + onError: (_err, _id, context) => {
111 + if (context?.previous) {
112 + queryClient.setQueryData(triggerKeys.list(), context.previous);
113 + }
114 + },
115 +
116 + onSuccess: () => {
117 + queryClient.invalidateQueries({ queryKey: triggerKeys.lists() });
118 + },
119 + });
120 + };
frontend/lib/api/client.ts
@@ -1,4 +1,4 @@
1 - import * as SecureStore from 'expo-secure-store';
1 + import * as SecureStore from "expo-secure-store";
2 2 import {
3 3 User,
4 4 AuthResponse,
@@ -11,8 +11,9 @@ UserUpdateResponse,
11 11 MoodComponentPayload,
12 12 MoodEntryPayload,
13 13 SleepRecordPayload,
14 - PaginatedResponse
15 - } from '@/lib/api/types';
14 + CreateTriggerPayload,
15 + PaginatedResponse,
16 + } from "@/lib/api/types";
16 17
17 18 const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL;
18 19 const TOKEN_KEY = process.env.EXPO_PUBLIC_TOKEN_KEY;
@@ -22,7 +23,7 @@ private async getStoredToken(): Promise<string | null> {
22 23 try {
23 24 return await SecureStore.getItemAsync(TOKEN_KEY);
24 25 } catch (error) {
25 - console.error('Failed to get stored token:', error);
26 + console.error("Failed to get stored token:", error);
26 27 return null;
27 28 }
28 29 }
@@ -31,7 +32,7 @@ private async storeToken(token: string): Promise<void> {
31 32 try {
32 33 await SecureStore.setItemAsync(TOKEN_KEY, token);
33 34 } catch (error) {
34 - console.error('Failed to store token:', error);
35 + console.error("Failed to store token:", error);
35 36 throw error;
36 37 }
37 38 }
@@ -40,7 +41,7 @@ private async removeToken(): Promise<void> {
40 41 try {
41 42 await SecureStore.deleteItemAsync(TOKEN_KEY);
42 43 } catch (error) {
43 - console.error('Failed to remove token:', error);
44 + console.error("Failed to remove token:", error);
44 45 }
45 46 }
46 47
@@ -51,7 +52,7 @@ }
51 52
52 53 private async request<T>(
53 54 endpoint: string,
54 - options: RequestInit = {}
55 + options: RequestInit = {},
55 56 ): Promise<T> {
56 57 const token = await this.getStoredToken();
57 58 const isFormData = options.body instanceof FormData;
@@ -60,7 +61,7 @@ const config: RequestInit = {
60 61 ...options,
61 62 headers: {
62 63 ...(token && { Authorization: `Bearer ${token}` }),
63 - ...(!isFormData && { 'Content-Type': 'application/json' }),
64 + ...(!isFormData && { "Content-Type": "application/json" }),
64 65 ...options.headers,
65 66 },
66 67 };
@@ -68,10 +69,11 @@
68 69 const response = await fetch(`${API_BASE_URL}${endpoint}`, config);
69 70
70 71 if (!response.ok) {
71 - const error = await response.json()
72 - .catch(() => ({ error: 'Network error' }));
72 + const error = await response
73 + .json()
74 + .catch(() => ({ error: "Network error" }));
73 75
74 - console.error("[API]: ", error, response.status)
76 + console.error("[API]: ", error, response.status);
75 77
76 78 throw new Error(error.error || `HTTP ${response.status}`);
77 79 }
@@ -80,9 +82,12 @@ return response.json();
80 82 }
81 83
82 84 // Auth
83 - async login(email: string, password: string): Promise<AuthResponse | { isActive: boolean }> {
84 - const response = await this.request<AuthResponse>('/auth/login', {
85 - method: 'POST',
85 + async login(
86 + email: string,
87 + password: string,
88 + ): Promise<AuthResponse | { isActive: boolean }> {
89 + const response = await this.request<AuthResponse>("/auth/login", {
90 + method: "POST",
86 91 body: JSON.stringify({ email, password }),
87 92 });
88 93
@@ -94,21 +99,21 @@ return response;
94 99 }
95 100
96 101 async activate(code: number): Promise<ActivateResponse> {
97 - return this.request('/auth/activate', {
98 - method: 'POST',
102 + return this.request("/auth/activate", {
103 + method: "POST",
99 104 body: JSON.stringify({ code }),
100 105 });
101 106 }
102 107
103 108 async requestActivateCode(): Promise<void> {
104 - return this.request('/auth/resend_code', {
105 - method: 'POST'
106 - })
109 + return this.request("/auth/resend_code", {
110 + method: "POST",
111 + });
107 112 }
108 113
109 114 async signup(user: SignUpPayload): Promise<AuthResponse> {
110 - const response = await this.request<AuthResponse>('/users', {
111 - method: 'POST',
115 + const response = await this.request<AuthResponse>("/users", {
116 + method: "POST",
112 117 body: JSON.stringify({ user }),
113 118 });
114 119
@@ -117,22 +122,25 @@ return response;
117 122 }
118 123
119 124 async forgotPassword(email: string): Promise<ResetPasswordRequestResponse> {
120 - return this.request('/auth/forgot-password', {
121 - method: 'POST',
125 + return this.request("/auth/forgot-password", {
126 + method: "POST",
122 127 body: JSON.stringify({ email }),
123 128 });
124 129 }
125 130
126 - async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
127 - return this.request('/auth/reset-password', {
128 - method: 'POST',
131 + async resetPassword(
132 + token: string,
133 + password: string,
134 + ): Promise<ResetPasswordResponse> {
135 + return this.request("/auth/reset-password", {
136 + method: "POST",
129 137 body: JSON.stringify({ token, newPassword: password }),
130 138 });
131 139 }
132 140
133 141 async verifyToken(): Promise<VerifyTokenResponse> {
134 142 try {
135 - return await this.request('/auth/verify');
143 + return await this.request("/auth/verify");
136 144 } catch (error) {
137 145 // Token is invalid or expired
138 146 await this.removeToken();
@@ -147,33 +155,35 @@
147 155 // User-related
148 156 async updateUser(user: UserUpdatePayload): Promise<User> {
149 157 return await this.request(`/users/${user.id}`, {
150 - method: 'PUT',
151 - body: JSON.stringify({ user })
152 - })
158 + method: "PUT",
159 + body: JSON.stringify({ user }),
160 + });
153 161 }
154 162
155 163 async avatar(formData: FormData): Promise<User> {
156 164 return await this.request(`/users/me/avatar`, {
157 - method: 'POST',
165 + method: "POST",
158 166 body: formData,
159 167 headers: {
160 - 'Content-Type': 'multipart/form-data',
168 + "Content-Type": "multipart/form-data",
161 169 } as any,
162 - })
170 + });
163 171 }
164 172
165 173 async removeAvatar(): Promise<void> {
166 174 return await this.request(`/users/me/avatar`, {
167 - method: 'DELETE'
175 + method: "DELETE",
168 176 });
169 177 }
170 178
171 179 // Mood-related
172 - async createMoodEntry(mood: CreateMoodEntryPayload): Promise<MoodEntryResponse> {
180 + async createMoodEntry(
181 + mood: CreateMoodEntryPayload,
182 + ): Promise<MoodEntryResponse> {
173 183 return await this.request(`/moods`, {
174 - method: 'POST',
175 - body: JSON.stringify({ mood })
176 - })
184 + method: "POST",
185 + body: JSON.stringify({ mood }),
186 + });
177 187 }
178 188
179 189 async getMoodEntries(params?: {
@@ -183,13 +193,13 @@ page?: number;
183 193 limit?: number;
184 194 }): Promise<PaginatedResponse<MoodEntry>> {
185 195 const query = new URLSearchParams();
186 - if (params?.from) query.set('from', params.from);
187 - if (params?.to) query.set('to', params.to);
188 - if (params?.page) query.set('page', String(params.page));
189 - if (params?.limit) query.set('limit', String(params.limit));
196 + if (params?.from) query.set("from", params.from);
197 + if (params?.to) query.set("to", params.to);
198 + if (params?.page) query.set("page", String(params.page));
199 + if (params?.limit) query.set("limit", String(params.limit));
190 200
191 201 const qs = query.toString();
192 - return this.request(`/moods${qs ? `?${qs}` : ''}`);
202 + return this.request(`/moods${qs ? `?${qs}` : ""}`);
193 203 }
194 204
195 205 async getMoodEntry(id: string): Promise<MoodEntry> {
@@ -197,15 +207,17 @@ return this.request(`/moods/${id}`);
197 207 }
198 208
199 209 async deleteMoodEntry(id: string): Promise<void> {
200 - return this.request(`/moods/${id}`, { method: 'DELETE' });
210 + return this.request(`/moods/${id}`, { method: "DELETE" });
201 211 }
202 212
203 213 // Sleep Record
204 - async createSleepRecord(sleepRecord: CreateSleepRecordPayload): Promise<SleepRecordResponse> {
214 + async createSleepRecord(
215 + sleepRecord: CreateSleepRecordPayload,
216 + ): Promise<SleepRecordResponse> {
205 217 return await this.request(`/sleep_records`, {
206 - method: 'POST',
207 - body: JSON.stringify({ sleepRecord })
208 - })
218 + method: "POST",
219 + body: JSON.stringify({ sleepRecord }),
220 + });
209 221 }
210 222
211 223 async getSleepRecords(params?: {
@@ -215,13 +227,45 @@ page?: number;
215 227 limit?: number;
216 228 }): Promise<PaginatedResponse<SleepRecord>> {
217 229 const query = new URLSearchParams();
218 - if (params?.from) query.set('from', params.from);
219 - if (params?.to) query.set('to', params.to);
220 - if (params?.page) query.set('page', String(params.page));
221 - if (params?.limit) query.set('limit', String(params.limit));
230 + if (params?.from) query.set("from", params.from);
231 + if (params?.to) query.set("to", params.to);
232 + if (params?.page) query.set("page", String(params.page));
233 + if (params?.limit) query.set("limit", String(params.limit));
234 +
235 + const qs = query.toString();
236 + return this.request(`/sleep_records${qs ? `?${qs}` : ""}`);
237 + }
238 +
239 + // Trigger
240 + async createTrigger(trigger: CreateTriggerPayload): Promise<TriggerResponse> {
241 + return await this.request(`/triggers`, {
242 + method: "POST",
243 + body: JSON.stringify({ trigger }),
244 + });
245 + }
246 +
247 + async getTriggers(params?: {
248 + from?: string;
249 + to?: string;
250 + page?: number;
251 + limit?: number;
252 + }): Promise<PaginatedResponse<Trigger>> {
253 + const query = new URLSearchParams();
254 + if (params?.from) query.set("from", params.from);
255 + if (params?.to) query.set("to", params.to);
256 + if (params?.page) query.set("page", String(params.page));
257 + if (params?.limit) query.set("limit", String(params.limit));
222 258
223 259 const qs = query.toString();
224 - return this.request(`/sleep_records${qs ? `?${qs}` : ''}`);
260 + return this.request(`/triggers${qs ? `?${qs}` : ""}`);
261 + }
262 +
263 + async getTrigger(id: string): Promise<Trigger> {
264 + return this.request(`/triggers/${id}`);
265 + }
266 +
267 + async deleteTrigger(id: string): Promise<void> {
268 + return this.request(`/triggers/${id}`, { method: "DELETE" });
225 269 }
226 270 }
227 271
frontend/lib/api/types.ts
@@ -7,36 +7,36 @@ updatedAt: Date;
7 7 avatarKey?: string;
8 8 avatarURL?: string;
9 9 active: boolean;
10 - };
10 + }
11 11
12 12 // Auth
13 13 export interface AuthResponse {
14 14 token: string;
15 15 user: User;
16 - };
16 + }
17 17
18 18 export interface VerifyTokenResponse {
19 19 valid: boolean;
20 20 userId?: number;
21 21 email?: string;
22 - user: Pick<User, 'firstName' | 'lastName'>;
23 - };
22 + user: Pick<User, "firstName" | "lastName">;
23 + }
24 24
25 25 export interface SignUpPayload {
26 26 firstName: string;
27 27 lastName?: string;
28 28 email: string;
29 29 password: string;
30 - };
30 + }
31 31
32 32 export interface ResetPasswordRequestResponse {
33 33 message: string;
34 34 token?: string;
35 - };
35 + }
36 36
37 37 export interface ResetPasswordResponse {
38 38 message: string;
39 - };
39 + }
40 40
41 41 export interface ActivateResponse {
42 42 activated: boolean;
@@ -48,14 +48,14 @@ id: number;
48 48 }
49 49
50 50 export interface UserUpdateResponse {
51 - user: User
52 - };
51 + user: User;
52 + }
53 53
54 54 // Mood entries
55 55 export interface MoodComponentPayload {
56 56 component: string;
57 57 intensity: string;
58 - };
58 + }
59 59
60 60 export interface CreateMoodEntryPayload {
61 61 annotation: string;
@@ -64,8 +64,8 @@ selectedMood: string;
64 64 anxietyLevel: number;
65 65 energyLevel: number;
66 66 stressLevel: number;
67 - moodComponents: MoodComponentPayload[]
68 - };
67 + moodComponents: MoodComponentPayload[];
68 + }
69 69
70 70 export interface MoodComponent {
71 71 id: number;
@@ -94,7 +94,7 @@ export interface SleepRecordPayload {
94 94 average: number;
95 95 annotations: string;
96 96 date: Date;
97 - };
97 + }
98 98
99 99 export interface SleepRecord {
100 100 id: number;
@@ -105,10 +105,27 @@ createdAt: Date;
105 105 updatedAt: Date;
106 106 }
107 107
108 + // Trigger
109 +
110 + export interface Trigger {
111 + id: number;
112 + comment: string;
113 + category: string;
114 + moment: Date;
115 + createdAt: Date;
116 + updatedAt: Date;
117 + }
118 +
119 + export interface CreateTriggerPayload {
120 + comment: string;
121 + category: string;
122 + moment: Date;
123 + }
124 +
108 125 // Generic stuff
109 126 export interface PaginatedResponse<T> {
110 127 entries: T[];
111 128 total: number;
112 129 page: number;
113 130 nextPage: number | null;
114 - };
131 + }