shelflife-react-hooks 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +23 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +23 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +36 -36
- package/src/context/AuthContext.tsx +161 -161
- package/src/context/InviteContext.tsx +74 -74
- package/src/context/ProductContext.tsx +131 -121
- package/src/context/RunningLowContext.tsx +100 -100
- package/src/context/ShoppingListContext.tsx +76 -76
- package/src/context/StorageContext.tsx +105 -105
- package/src/context/StorageItemContext.tsx +157 -157
- package/src/context/StorageMemberContext.tsx +84 -84
- package/src/context/UserContext.tsx +109 -109
- package/src/context/__tests__/contexts.test.tsx +370 -370
- package/src/context/api/authApi.ts +155 -155
- package/src/context/api/inviteApi.ts +65 -65
- package/src/context/api/productApi.ts +223 -201
- package/src/context/api/requestState.ts +24 -24
- package/src/context/api/runningLowApi.ts +141 -141
- package/src/context/api/shoppingListApi.ts +161 -159
- package/src/context/api/storageApi.ts +166 -166
- package/src/context/api/storageItemApi.ts +260 -260
- package/src/context/api/storageMemberApi.ts +84 -84
- package/src/context/api/userApi.ts +161 -161
- package/src/context/http.ts +22 -22
- package/src/index.ts +21 -21
- package/src/type/PaginatedResponse.ts +8 -8
- package/src/type/auth.ts +79 -79
- package/src/type/base.ts +21 -21
- package/src/type/item.ts +12 -12
- package/src/type/member.ts +6 -6
- package/src/type/models.ts +56 -56
- package/src/type/product.ts +11 -11
- package/src/type/requests.ts +60 -60
- package/src/type/runninglow.ts +13 -13
- package/src/type/shoppingList.ts +13 -13
- package/src/type/storage.ts +7 -7
- package/src/type/user.ts +11 -11
- package/tsconfig.json +46 -46
- package/tsup.config.ts +10 -10
- package/vitest.config.ts +8 -8
|
@@ -1,201 +1,223 @@
|
|
|
1
|
-
import type { Product } from '../../type/models.js';
|
|
2
|
-
import type { PaginatedResponse } from '../../type/PaginatedResponse.js';
|
|
3
|
-
import type { ProductCreateError } from '../../type/product.js';
|
|
4
|
-
import type { CreateProductRequest, UpdateProductRequest } from '../../type/requests.js';
|
|
5
|
-
import { buildAuthHeaders, normalizeBaseUrl, readArrayBuffer, readJson } from '../http.js';
|
|
6
|
-
import { runWithRequestState, type RequestStateHandlers } from './requestState.js';
|
|
7
|
-
|
|
8
|
-
type ProductApiConfig = RequestStateHandlers & {
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
token: string | null;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
next
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
config.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
): Promise<
|
|
86
|
-
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
87
|
-
const response = await fetch(`${normalizedBaseUrl}/api/products`, {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
id
|
|
186
|
-
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (!response.ok) {
|
|
199
|
-
throw new Error('Failed to
|
|
200
|
-
}
|
|
201
|
-
|
|
1
|
+
import type { Product } from '../../type/models.js';
|
|
2
|
+
import type { PaginatedResponse } from '../../type/PaginatedResponse.js';
|
|
3
|
+
import type { ProductCreateError } from '../../type/product.js';
|
|
4
|
+
import type { CreateProductRequest, UpdateProductRequest } from '../../type/requests.js';
|
|
5
|
+
import { buildAuthHeaders, normalizeBaseUrl, readArrayBuffer, readJson } from '../http.js';
|
|
6
|
+
import { runWithRequestState, type RequestStateHandlers } from './requestState.js';
|
|
7
|
+
|
|
8
|
+
type ProductApiConfig = RequestStateHandlers & {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
token: string | null;
|
|
11
|
+
setCategories: (value: string[] | ((items: string[]) => string[])) => void;
|
|
12
|
+
setProducts: (value: Product[] | ((items: Product[]) => Product[])) => void;
|
|
13
|
+
setProduct: (value: Product | null | ((prev: Product | null) => Product | null)) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const updateById = (items: Product[], updated: Product): Product[] => {
|
|
17
|
+
const index = items.findIndex((item) => item.id === updated.id);
|
|
18
|
+
if (index === -1) {
|
|
19
|
+
return [updated, ...items];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const next = [...items];
|
|
23
|
+
next[index] = updated;
|
|
24
|
+
return next;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const fetchProductsRequest = async (
|
|
28
|
+
config: ProductApiConfig,
|
|
29
|
+
search: string,
|
|
30
|
+
size: number,
|
|
31
|
+
page: number
|
|
32
|
+
): Promise<PaginatedResponse<Product>> => runWithRequestState(config, async () => {
|
|
33
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
34
|
+
let url = `${normalizedBaseUrl}/api/products?search=${search}`;
|
|
35
|
+
if (size > 0)
|
|
36
|
+
url += `&page=${page}&size=${size}`;
|
|
37
|
+
|
|
38
|
+
const response = await fetch(url, {
|
|
39
|
+
headers: buildAuthHeaders(config.token)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error('Failed to fetch products');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const payload = await readJson<PaginatedResponse<Product>>(response);
|
|
47
|
+
if (payload) {
|
|
48
|
+
config.setProducts(payload.data);
|
|
49
|
+
return payload;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { data: [], currentPage: 0, hasNext: false, hasPrevious: false, pageSize: 0, totalItems: 0, totalPages: 0 };
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const fetchProductRequest = async (
|
|
56
|
+
config: ProductApiConfig,
|
|
57
|
+
id: number
|
|
58
|
+
): Promise<Product | null> => runWithRequestState(config, async () => {
|
|
59
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
60
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/${id}`, {
|
|
61
|
+
headers: buildAuthHeaders(config.token)
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (response.status === 404) {
|
|
65
|
+
config.setProduct(null);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error('Failed to fetch product');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const payload = await readJson<Product>(response);
|
|
74
|
+
if (!payload) {
|
|
75
|
+
throw new Error('Product response missing data');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
config.setProduct(payload);
|
|
79
|
+
config.setProducts((previous) => updateById(previous, payload));
|
|
80
|
+
return payload;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const fetchCategoriesRequest = async (
|
|
84
|
+
config: ProductApiConfig
|
|
85
|
+
): Promise<string[] | null> => runWithRequestState(config, async () => {
|
|
86
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
87
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/categories`, {
|
|
88
|
+
headers: buildAuthHeaders(config.token)
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error('Failed to fetch categories');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const payload = await readJson<string[]>(response);
|
|
96
|
+
if (!payload) {
|
|
97
|
+
throw new Error('Category response missing data');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
config.setCategories(payload);
|
|
101
|
+
return payload;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const createProductRequest = async (
|
|
105
|
+
config: ProductApiConfig,
|
|
106
|
+
dto: CreateProductRequest
|
|
107
|
+
): Promise<Product> => runWithRequestState(config, async () => {
|
|
108
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
109
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
...buildAuthHeaders(config.token),
|
|
113
|
+
'Content-Type': 'application/json'
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify(dto)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
const err = await readJson<ProductCreateError>(response);
|
|
120
|
+
|
|
121
|
+
if (err?.name || err?.barcode || err?.category || err?.expirationDaysDelta)
|
|
122
|
+
throw err;
|
|
123
|
+
|
|
124
|
+
throw new Error('Failed to create product');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const payload = await readJson<Product>(response);
|
|
128
|
+
if (!payload) {
|
|
129
|
+
throw new Error('Create product response missing data');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
config.setProducts((previous) => [payload, ...previous]);
|
|
133
|
+
config.setProduct(payload);
|
|
134
|
+
return payload;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const updateProductRequest = async (
|
|
138
|
+
config: ProductApiConfig,
|
|
139
|
+
id: number,
|
|
140
|
+
dto: UpdateProductRequest
|
|
141
|
+
): Promise<Product> => runWithRequestState(config, async () => {
|
|
142
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
143
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/${id}`, {
|
|
144
|
+
method: 'PATCH',
|
|
145
|
+
headers: {
|
|
146
|
+
...buildAuthHeaders(config.token),
|
|
147
|
+
'Content-Type': 'application/json'
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify(dto)
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const err = await readJson<ProductCreateError>(response);
|
|
154
|
+
|
|
155
|
+
if (err?.name || err?.barcode || err?.category || err?.expirationDaysDelta)
|
|
156
|
+
throw err;
|
|
157
|
+
|
|
158
|
+
throw new Error('Failed to update product');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const payload = await readJson<Product>(response);
|
|
162
|
+
if (!payload) {
|
|
163
|
+
throw new Error('Update product response missing data');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
config.setProducts((previous) => updateById(previous, payload));
|
|
167
|
+
config.setProduct(payload);
|
|
168
|
+
return payload;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const deleteProductRequest = async (
|
|
172
|
+
config: ProductApiConfig,
|
|
173
|
+
id: number
|
|
174
|
+
): Promise<void> => runWithRequestState(config, async () => {
|
|
175
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
176
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/${id}`, {
|
|
177
|
+
method: 'DELETE',
|
|
178
|
+
headers: buildAuthHeaders(config.token)
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error('Failed to delete product');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
config.setProducts((previous) => previous.filter((item) => item.id !== id));
|
|
186
|
+
config.setProduct((current) => (current?.id === id ? null : current));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export const getProductIconRequest = async (
|
|
190
|
+
config: ProductApiConfig,
|
|
191
|
+
id: number
|
|
192
|
+
): Promise<ArrayBuffer> => runWithRequestState(config, async () => {
|
|
193
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
194
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/${id}/icon`, {
|
|
195
|
+
headers: buildAuthHeaders(config.token)
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
throw new Error('Failed to fetch product icon');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return readArrayBuffer(response);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
export const uploadProductIconRequest = async (
|
|
206
|
+
config: ProductApiConfig,
|
|
207
|
+
id: number,
|
|
208
|
+
file: Blob
|
|
209
|
+
): Promise<void> => runWithRequestState(config, async () => {
|
|
210
|
+
const normalizedBaseUrl = normalizeBaseUrl(config.baseUrl);
|
|
211
|
+
const formData = new FormData();
|
|
212
|
+
formData.append('pfp', file);
|
|
213
|
+
|
|
214
|
+
const response = await fetch(`${normalizedBaseUrl}/api/products/${id}/icon`, {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: buildAuthHeaders(config.token),
|
|
217
|
+
body: formData
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
throw new Error('Failed to upload product icon');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
export type RequestStateHandlers = {
|
|
2
|
-
setIsLoading: (value: boolean) => void;
|
|
3
|
-
setIsError: (value: boolean) => void;
|
|
4
|
-
setError: (error: any | null) => void;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const runWithRequestState = async <T>(
|
|
8
|
-
handlers: RequestStateHandlers,
|
|
9
|
-
request: () => Promise<T>
|
|
10
|
-
): Promise<T> => {
|
|
11
|
-
handlers.setIsLoading(true);
|
|
12
|
-
handlers.setIsError(false);
|
|
13
|
-
handlers.setError(null);
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
return await request();
|
|
17
|
-
} catch (err) {
|
|
18
|
-
handlers.setIsError(true);
|
|
19
|
-
handlers.setError(err);
|
|
20
|
-
throw err;
|
|
21
|
-
} finally {
|
|
22
|
-
handlers.setIsLoading(false);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
1
|
+
export type RequestStateHandlers = {
|
|
2
|
+
setIsLoading: (value: boolean) => void;
|
|
3
|
+
setIsError: (value: boolean) => void;
|
|
4
|
+
setError: (error: any | null) => void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const runWithRequestState = async <T>(
|
|
8
|
+
handlers: RequestStateHandlers,
|
|
9
|
+
request: () => Promise<T>
|
|
10
|
+
): Promise<T> => {
|
|
11
|
+
handlers.setIsLoading(true);
|
|
12
|
+
handlers.setIsError(false);
|
|
13
|
+
handlers.setError(null);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return await request();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
handlers.setIsError(true);
|
|
19
|
+
handlers.setError(err);
|
|
20
|
+
throw err;
|
|
21
|
+
} finally {
|
|
22
|
+
handlers.setIsLoading(false);
|
|
23
|
+
}
|
|
24
|
+
};
|