Skip to content

Commit e2e2984

Browse files
committed
code cleanup
1 parent 730b206 commit e2e2984

File tree

11 files changed

+348
-129
lines changed

11 files changed

+348
-129
lines changed

client-v2/src/components/messages/UpsertSample.tsx

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
2020
import { useForm, Controller } from "react-hook-form";
2121

2222
import Styles from "./UpsertSample.style";
23-
import { createSampleAsync, updateSampleAsync, searchSamplesAsync, getLatestSamplesAsync, getAllSamplesAsync, getTotalSamplesAsync, resetCreateState, resetUpdateState, getSampleDetailsAsync, resetSampleDetailsState } from "../../store/slices/SearchSlice";
23+
import { upsertSampleAsync, searchSamplesAsync, getLatestSamplesAsync, getAllSamplesAsync, getTotalSamplesAsync, resetUpsertState, getSampleDetailsAsync, resetSampleDetailsState } from "../../store/slices/SearchSlice";
2424
import { AppDispatch, RootState } from "../../store/store";
2525
import { useDispatch, useSelector } from "react-redux";
2626
import { Record } from "../../types/Record";
@@ -43,25 +43,19 @@ const UpsertSample = (props: UpsertProps) => {
4343
const classes = Styles();
4444

4545
useEffect(() => {
46-
const createSucceeded = search.createSample.status === 'succeeded';
47-
const updateSucceeded = search.updateSample.status === 'succeeded';
46+
const upsertSucceeded = search.upsertSample.status === 'succeeded';
4847

49-
if (createSucceeded || updateSucceeded) {
48+
if (upsertSucceeded) {
5049
setOpen(false);
5150

52-
// Reset the states immediately to prevent infinite loop
53-
if (createSucceeded) {
54-
dispatch(resetCreateState());
55-
}
56-
if (updateSucceeded) {
57-
dispatch(resetUpdateState());
58-
}
51+
// Reset the state immediately to prevent infinite loop
52+
dispatch(resetUpsertState());
5953

6054
// Refresh the search results based on current state
6155
const urlParams = new URLSearchParams(window.location.search);
6256
const qParam = urlParams.get('q');
6357

64-
// Update total samples count
58+
// Update total samples count (only needed for create operations, but getTotalSamplesAsync will get the correct count regardless)
6559
dispatch(getTotalSamplesAsync());
6660

6761
// Small delay to ensure operations are processed
@@ -77,7 +71,7 @@ const UpsertSample = (props: UpsertProps) => {
7771
}
7872
}, 100);
7973
}
80-
}, [search.createSample.status, search.updateSample.status, setOpen, dispatch]);
74+
}, [search.upsertSample.status, setOpen, dispatch]);
8175

8276
// form validation hook
8377
const validationSchema = Yup.object().shape({
@@ -168,11 +162,10 @@ const UpsertSample = (props: UpsertProps) => {
168162
const values = getValues();
169163
// upsert
170164
const record = new Record(values.name, values.url, values.description, values.notes ?? '', values.details);
171-
if (isEditMode && editSample) {
172-
dispatch(updateSampleAsync(JSON.stringify(record)));
173-
} else {
174-
dispatch(createSampleAsync(JSON.stringify(record)));
175-
}
165+
dispatch(upsertSampleAsync({
166+
payload: JSON.stringify(record),
167+
isCreate: !isEditMode
168+
}));
176169
reset();
177170
}
178171
if (target === 'cancel') {
@@ -192,21 +185,21 @@ const UpsertSample = (props: UpsertProps) => {
192185
<DialogBody>
193186
<DialogTitle>{isEditMode ? 'Edit Item' : 'New Item'}</DialogTitle>
194187
<DialogContent className={classes.content}>
195-
{(search.createSample.status === 'failed' || search.updateSample.status === 'failed' ||
188+
{(search.upsertSample.status === 'failed' ||
196189
(isEditMode && search.sampleDetails.status === 'failed')) &&
197190
<>
198191
<p className={classes.error}>Failed to {
199192
isEditMode && search.sampleDetails.status === 'failed' ? 'load sample details' :
200-
isEditMode ? 'update' : 'create'
193+
search.upsertSample.isCreate ? 'create' : 'update'
201194
} the sample. Please try again.</p>
202195
<code>{
203196
isEditMode && search.sampleDetails.status === 'failed' ? search.sampleDetails.error :
204-
isEditMode ? search.updateSample.error : search.createSample.error
197+
search.upsertSample.error
205198
}</code>
206199
</>
207200
}
208-
{(search.createSample.status === 'loading' || search.updateSample.status === 'loading') &&
209-
<Field validationMessage={`${isEditMode ? 'Updating' : 'Creating'} item, please wait ...`} validationState="none">
201+
{search.upsertSample.status === 'loading' &&
202+
<Field validationMessage={`${search.upsertSample.isCreate ? 'Creating' : 'Updating'} item, please wait ...`} validationState="none">
210203
<ProgressBar />
211204
</Field>
212205
}
@@ -215,8 +208,8 @@ const UpsertSample = (props: UpsertProps) => {
215208
<ProgressBar />
216209
</Field>
217210
}
218-
{(search.createSample.status === 'succeeded' || search.updateSample.status === 'succeeded') &&
219-
<p>Record {isEditMode ? 'updated' : 'created'}.</p>
211+
{search.upsertSample.status === 'succeeded' &&
212+
<p>Record {search.upsertSample.isCreate ? 'created' : 'updated'}.</p>
220213
}
221214
<Field
222215
required

client-v2/src/store/slices/SearchSlice.ts

Lines changed: 69 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2-
import { HttpClient, getDataAPIUrl } from '../../utils/httpClient';
2+
import { HttpClient } from '../../utils/httpClient';
33
import SearchState from './SearchState';
44
import Sample from '../../types/Sample';
5+
import { RootState } from '../store';
56

67
// Define the delay function
78
const delay = (ms: number | undefined) => new Promise(resolve => setTimeout(resolve, ms));
89

910
// async search total samples
1011
export const getTotalSamplesAsync = createAsyncThunk<number>('search/getTotalSamples', async () => {
11-
const response = await HttpClient.get(`${getDataAPIUrl()}countSamples`, {
12-
withCredentials: false,
12+
const response = await HttpClient.get(`./api/countSamples`, {
1313
headers: {
1414
'Content-Type': 'application/json',
1515
'Accept': 'application/json',
@@ -20,8 +20,7 @@ export const getTotalSamplesAsync = createAsyncThunk<number>('search/getTotalSam
2020

2121
// async list all samples
2222
export const getAllSamplesAsync = createAsyncThunk<Sample[]>('search/getAllSamplesAsync', async () => {
23-
const response = await HttpClient.get(`${getDataAPIUrl()}samples`, {
24-
withCredentials: false,
23+
const response = await HttpClient.get(`./api/samples`, {
2524
headers: {
2625
'Content-Type': 'application/json',
2726
'Accept': 'application/json',
@@ -32,8 +31,7 @@ export const getAllSamplesAsync = createAsyncThunk<Sample[]>('search/getAllSampl
3231

3332
// async list latest samples
3433
export const getLatestSamplesAsync = createAsyncThunk<Sample[]>('search/getLatestSamples', async () => {
35-
const response = await HttpClient.get(`${getDataAPIUrl()}latestSamples`, {
36-
withCredentials: false,
34+
const response = await HttpClient.get(`./api/latestSamples`, {
3735
headers: {
3836
'Content-Type': 'application/json',
3937
'Accept': 'application/json',
@@ -44,8 +42,7 @@ export const getLatestSamplesAsync = createAsyncThunk<Sample[]>('search/getLates
4442

4543
// async search specific samples
4644
export const searchSamplesAsync = createAsyncThunk<Sample[], string>('search/searchSamplesAsync', async (query: string) => {
47-
const response = await HttpClient.get(`${getDataAPIUrl()}findSamples?text=${query}`, {
48-
withCredentials: false,
45+
const response = await HttpClient.get(`./api/findSamples?text=${query}`, {
4946
headers: {
5047
'Content-Type': 'application/json',
5148
'Accept': 'application/json',
@@ -55,15 +52,23 @@ export const searchSamplesAsync = createAsyncThunk<Sample[], string>('search/sea
5552
});
5653

5754
// delete a sample
58-
export const deleteSampleAsync = createAsyncThunk<number, string>('search/deleteSampleAsync', async (id: string) => {
55+
export const deleteSampleAsync = createAsyncThunk<number, string, { state: RootState }>('search/deleteSampleAsync', async (id: string, { getState }) => {
56+
const state = getState();
57+
const isAdmin = state.user.isAdmin;
58+
59+
const headers: Record<string, string> = {
60+
'Content-Type': 'application/json',
61+
'Accept': 'application/json',
62+
};
63+
64+
if (isAdmin) {
65+
headers['X-MS-API-ROLE'] = 'admin';
66+
}
67+
5968
await delay(1000).then(async () => { // for better user experience
60-
await HttpClient.delete(`${getDataAPIUrl()}deleteSample`, {
69+
await HttpClient.delete(`./api/deleteSample`, {
6170
data: { id: id, url: null },
62-
withCredentials: false,
63-
headers: {
64-
'Content-Type': 'application/json',
65-
'Accept': 'application/json',
66-
}
71+
headers
6772
});
6873

6974
});
@@ -72,8 +77,7 @@ export const deleteSampleAsync = createAsyncThunk<number, string>('search/delete
7277

7378
// get the details
7479
export const getSampleDetailsAsync = createAsyncThunk<Sample, string>('search/getSampleDetailsAsync', async (id: string) => {
75-
const response = await HttpClient.post(`${getDataAPIUrl()}sampleDetails`, { id: id, url: null }, {
76-
// withCredentials: false,
80+
const response = await HttpClient.post(`./api/sampleDetails`, { id: id, url: null }, {
7781
headers: {
7882
'Content-Type': 'application/json',
7983
'Accept': 'application/json',
@@ -83,28 +87,28 @@ export const getSampleDetailsAsync = createAsyncThunk<Sample, string>('search/ge
8387
return JSON.parse(sample);
8488
});
8589

86-
// create a sample
87-
export const createSampleAsync = createAsyncThunk<number, string>('search/createSampleAsync', async (payload: string) => {
88-
const response = await HttpClient.post(`${getDataAPIUrl()}createSample`, { payload: payload }, {
89-
// withCredentials: false,
90-
headers: {
91-
'Content-Type': 'application/json',
92-
'Accept': 'application/json',
93-
}
94-
});
95-
return response.data;
96-
});
90+
// upsert a sample (create or update)
91+
export const upsertSampleAsync = createAsyncThunk<
92+
{ id: number; isCreate: boolean },
93+
{ payload: string; isCreate: boolean },
94+
{ state: RootState }
95+
>('search/upsertSampleAsync', async ({ payload, isCreate }, { getState }) => {
96+
const state = getState();
97+
const isAdmin = state.user.isAdmin;
98+
99+
const headers: Record<string, string> = {
100+
'Content-Type': 'application/json',
101+
'Accept': 'application/json',
102+
};
103+
104+
if (isAdmin) {
105+
headers['X-MS-API-ROLE'] = 'admin';
106+
}
97107

98-
// update a sample
99-
export const updateSampleAsync = createAsyncThunk<number, string>('search/updateSampleAsync', async (payload:string) => {
100-
const response = await HttpClient.post(`${getDataAPIUrl()}createSample`, { payload: payload }, {
101-
// withCredentials: false,
102-
headers: {
103-
'Content-Type': 'application/json',
104-
'Accept': 'application/json',
105-
}
108+
const response = await HttpClient.post(`./api/upsertSample`, { payload: payload }, {
109+
headers
106110
});
107-
return response.data;
111+
return { id: response.data, isCreate };
108112
});
109113

110114

@@ -128,15 +132,11 @@ const initialState: SearchState = {
128132
error: undefined,
129133
sample: undefined
130134
},
131-
createSample: {
132-
status: 'idle',
133-
error: undefined,
134-
id: 0
135-
},
136-
updateSample: {
135+
upsertSample: {
137136
status: 'idle',
138137
error: undefined,
139-
id: 0
138+
id: 0,
139+
isCreate: false
140140
}
141141
};
142142

@@ -150,11 +150,8 @@ const SearchSlice = createSlice({
150150
resetDeleteState: (state) => {
151151
state.delete = initialState.delete;
152152
},
153-
resetUpdateState: (state) => {
154-
state.updateSample = initialState.updateSample;
155-
},
156-
resetCreateState: (state) => {
157-
state.createSample = initialState.createSample;
153+
resetUpsertState: (state) => {
154+
state.upsertSample = initialState.upsertSample;
158155
},
159156
resetSampleDetailsState: (state) => {
160157
state.sampleDetails = initialState.sampleDetails;
@@ -238,35 +235,33 @@ const SearchSlice = createSlice({
238235
state.sampleDetails.status = 'failed';
239236
state.sampleDetails.error = action.error.message;
240237
})
241-
// create sample
242-
.addCase(createSampleAsync.pending, (state) => {
243-
state.createSample.status = 'loading'
238+
// upsert sample (create or update)
239+
.addCase(upsertSampleAsync.pending, (state) => {
240+
state.upsertSample.status = 'loading';
244241
})
245-
.addCase(createSampleAsync.fulfilled, (state, action) => {
246-
state.createSample.status = 'succeeded';
247-
state.createSample.id = action.payload;
248-
// recount the total samples
249-
state.totalSamples.total = state.totalSamples.total + 1;
242+
.addCase(upsertSampleAsync.fulfilled, (state, action) => {
243+
state.upsertSample.status = 'succeeded';
244+
state.upsertSample.id = action.payload.id;
245+
state.upsertSample.isCreate = action.payload.isCreate;
246+
// increment total samples count only if it's a create operation
247+
if (action.payload.isCreate) {
248+
state.totalSamples.total = state.totalSamples.total + 1;
249+
}
250250
})
251-
.addCase(createSampleAsync.rejected, (state, action) => {
252-
state.createSample.status = 'failed';
253-
state.createSample.error = action.error.message;
254-
})
255-
// update sample
256-
.addCase(updateSampleAsync.pending, (state) => {
257-
state.updateSample.status = 'loading';
258-
})
259-
.addCase(updateSampleAsync.fulfilled, (state, action) => {
260-
state.updateSample.status = 'succeeded';
261-
state.updateSample.id = action.payload;
262-
})
263-
.addCase(updateSampleAsync.rejected, (state, action) => {
264-
state.updateSample.status = 'failed';
265-
state.updateSample.error = action.error.message;
251+
.addCase(upsertSampleAsync.rejected, (state, action) => {
252+
state.upsertSample.status = 'failed';
253+
state.upsertSample.error = action.error.message;
266254
})
267255
;
268256
}
269257
});
270258

271-
export const { resetSearchState, resetDeleteState, resetUpdateState, resetCreateState, resetSampleDetailsState } = SearchSlice.actions;
259+
export const { resetSearchState, resetDeleteState, resetUpsertState, resetSampleDetailsState } = SearchSlice.actions;
260+
261+
// Backward compatibility exports
262+
export const createSampleAsync = (payload: string) => upsertSampleAsync({ payload, isCreate: true });
263+
export const updateSampleAsync = (payload: string) => upsertSampleAsync({ payload, isCreate: false });
264+
export const resetCreateState = resetUpsertState;
265+
export const resetUpdateState = resetUpsertState;
266+
272267
export default SearchSlice;

client-v2/src/store/slices/SearchState.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,10 @@ export default interface SearchState {
2020
error: string | undefined;
2121
sample: Sample | undefined;
2222
},
23-
createSample: {
24-
status: 'idle' | 'loading' | 'succeeded' | 'failed';
25-
error: string | undefined;
26-
id: number;
27-
},
28-
updateSample: {
23+
upsertSample: {
2924
status: 'idle' | 'loading' | 'succeeded' | 'failed';
3025
error: string | undefined;
3126
id: number;
27+
isCreate: boolean;
3228
}
3329
}

client-v2/src/store/slices/UserSlice.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@ import UserState from "./UserState";
33
import sha256 from "crypto-js/sha256";
44
import { HttpClient } from "../../utils/httpClient";
55
import { User } from "../../types/User";
6-
import { stat } from "fs";
7-
import { isDataView } from "util/types";
86

97
// retrieve the current user and verify if it is authenticated
108
export const getUserAsync = createAsyncThunk<User>('user/getUserAsync', async () => {
119
const response = await HttpClient.get(`/.auth/me`, {
12-
withCredentials: false,
1310
headers: {
1411
'Content-Type': 'application/json',
1512
'Accept': 'application/json',

client-v2/src/utils/httpClient.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,6 @@ export class HttpClient {
103103
}
104104
}
105105

106-
function getDataAPIUrl() {
107-
if (import.meta.env.VITE_API_URL) {
108-
return import.meta.env.VITE_API_URL;
109-
}
110-
return './api/';
111-
}
112-
113106
// Export the retry function for custom use cases
114-
export { getDataAPIUrl, withRetry, DEFAULT_RETRY_CONFIG };
107+
export { withRetry, DEFAULT_RETRY_CONFIG };
115108
export type { RetryConfig };

0 commit comments

Comments
 (0)