Skip to content

Commit c2e19c2

Browse files
authored
feat(ui): common fetch hook (#62)
* feat(ui): add useFetch hook * feat(ui): improve apikey mangement * sdk: using shared useFetch * feat(ui): add useFetch hook * feat(ui): improve apikey mangement * sdk: using shared useFetch * chore: fix rebase * fix(ui): fix apiKey retrieve * feat: fix format
1 parent a45ab0d commit c2e19c2

File tree

15 files changed

+298
-341
lines changed

15 files changed

+298
-341
lines changed

packages/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"dev:start": "SET NODE_ENV=dev& node dist/index.js",
2020
"test": "node ./__tests__/api.test.js",
2121
"lint": "eslint . --ext .ts",
22-
"format": "prettier --write 'src/**/*.{js,ts}'"
22+
"format": "prettier --write \"src/**/*.{js,ts}\""
2323
},
2424
"bugs": {
2525
"url": "https://github.com/switchfeat-com/switchfeat/issues"

packages/api/src/routes/sdkAuthRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import * as sdkAuthService from "../services/sdkAuthService";
55
import * as auth from "../managers/auth/passportAuth";
66
import {
77
ApiResponseCodes,
8-
dateHelper,
98
dbManager,
9+
dateHelper,
1010
entityHelper,
1111
} from "@switchfeat/core";
1212
import {
@@ -88,7 +88,7 @@ export const sdkAuthRoutesWrapper = (
8888
setSuccessResponse(
8989
res,
9090
ApiResponseCodes.Success,
91-
{ apikey: apiKey },
91+
{ apiKey: apiKey },
9292
req,
9393
);
9494
} else {

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"start": "node dist/index.js",
2525
"test": "node ./__tests__/core.test.js",
2626
"lint": "eslint . --ext .ts",
27-
"format": "prettier --write 'src/**/*.{js,ts}'"
27+
"format": "prettier --write \"src/**/*.{js,ts}\""
2828
},
2929
"bugs": {
3030
"url": "https://github.com/switchfeat-com/switchfeat/issues"

packages/ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"build:tw:watch": "tailwindcss -i ./src/App.css -o ./src/output.css --watch",
3535
"prestart": "npm run build:tw",
3636
"lint": "eslint . --ext .ts,.tsx",
37-
"format": "prettier --write 'src/**/*.{ts,tsx}'"
37+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
3838
},
3939
"eslintConfig": {
4040
"extends": [

packages/ui/src/components/flags/CreateOrUpdateFlagDialog.tsx

Lines changed: 32 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { RulesBoard } from "./RulesBoard";
1212
import { RulesItem } from "./RulesItem";
1313
import { toast } from "react-hot-toast";
1414
import { Toast } from "../shared/NotificationProvider";
15+
import { useFetch } from "../../hooks/useFetch";
1516

1617
export interface CreateOrUpdateFlagDialogProps {
1718
open: boolean;
@@ -31,6 +32,7 @@ export const CreateOrUpdateFlagDialog: React.FC<
3132
const [enabled, setEnabled] = useState(false);
3233
const [showDelete, setShowDelete] = useState(false);
3334
const [rules, setRules] = useState<RuleModel[]>([]);
35+
const { doFetch } = useFetch();
3436

3537
useEffect(() => {
3638
if (!props.flag || !props.open) {
@@ -59,39 +61,27 @@ export const CreateOrUpdateFlagDialog: React.FC<
5961
formData.append("flagDescription", descriptionRef.current.value);
6062
}
6163

62-
fetch(`${keys.CLIENT_HOME_PAGE_URL}/api/flags/`, {
64+
doFetch<unknown, { message: string }>({
65+
url: `${keys.CLIENT_HOME_PAGE_URL}/api/flags/`,
6366
method: props.flag ? "PUT" : "POST",
64-
credentials: "include",
65-
headers: {
66-
Accept: "application/json",
67-
"Access-Control-Allow-Credentials": "true",
68-
"Access-Control-Allow-Origin": "true",
69-
},
70-
body: formData,
71-
})
72-
.then(async (resp) => {
73-
return resp.json();
74-
})
75-
.then((respJson) => {
76-
if (respJson.success as boolean) {
77-
props.refreshAll();
78-
props.setOpen(false);
79-
toast.success(`Flag operation successful!`, {
80-
subMessage: `Flag: ${props.flag?.name}`,
81-
} as Toast);
82-
} else {
83-
let msg = "Generic error occurred, please try again.";
84-
if (respJson.errorCode === "error_input") {
85-
msg = "One or more required information are missing.";
86-
} else if (respJson.errorCode === "error_alreadysaved") {
87-
msg = "There is already a flag with the same name.";
88-
}
89-
toast.error(msg);
67+
reqBody: formData,
68+
onError: (respJson) => {
69+
let msg = "Generic error occurred, please try again.";
70+
if (respJson.message === "error_input") {
71+
msg = "One or more required information are missing.";
72+
} else if (respJson.message === "error_alreadysaved") {
73+
msg = "There is already a flag with the same name.";
9074
}
91-
})
92-
.catch((error) => {
93-
console.log(error);
94-
});
75+
toast.error(msg);
76+
},
77+
onSuccess: () => {
78+
props.refreshAll();
79+
props.setOpen(false);
80+
toast.success(`Flag operation successful!`, {
81+
subMessage: `Flag: ${props.flag?.name}`,
82+
} as Toast);
83+
},
84+
});
9585
};
9686

9787
const onConfirmDelete = (): void => {
@@ -100,30 +90,19 @@ export const CreateOrUpdateFlagDialog: React.FC<
10090
formData.append("flagKey", props.flag.key);
10191
}
10292

103-
fetch(`${keys.CLIENT_HOME_PAGE_URL}/api/flags/`, {
93+
doFetch<unknown, { message: string }>({
94+
url: `${keys.CLIENT_HOME_PAGE_URL}/api/flags/`,
10495
method: "DELETE",
105-
credentials: "include",
106-
headers: {
107-
Accept: "application/json",
108-
"Access-Control-Allow-Credentials": "true",
109-
"Access-Control-Allow-Origin": "true",
110-
},
111-
body: formData,
112-
})
113-
.then(async (resp) => {
114-
return resp.json();
115-
})
116-
.then((respJson) => {
117-
if (respJson.success as boolean) {
118-
setShowDelete(false);
119-
props.refreshAll();
120-
toast.success("Flag deleted.");
121-
}
122-
})
123-
.catch((error) => {
124-
console.log(error);
96+
reqBody: formData,
97+
onError: () => {
12598
toast.error("Error deleting flag.");
126-
});
99+
},
100+
onSuccess: () => {
101+
setShowDelete(false);
102+
props.refreshAll();
103+
toast.success("Flag deleted.");
104+
},
105+
});
127106
};
128107

129108
const deleteFlagProps: ConfirmationDialogProps = {

packages/ui/src/components/flags/FlagsItem.tsx

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "./CreateOrUpdateFlagDialog";
1212
import { toast } from "react-hot-toast";
1313
import { Toast } from "../shared/NotificationProvider";
14+
import { useFetch } from "../../hooks/useFetch";
1415

1516
export const FlagsItem: React.FC<{
1617
flag: FlagModel;
@@ -20,6 +21,7 @@ export const FlagsItem: React.FC<{
2021
const [pendingSwitchEnabled, setPendingSwitchEnabled] = useState(false);
2122
const [showConfirmation, setShowConfirmation] = useState(false);
2223
const cancelButtonRef = useRef(null);
24+
const { doFetch } = useFetch();
2325

2426
const [openEdit, setOpenEdit] = useState(false);
2527

@@ -43,40 +45,28 @@ export const FlagsItem: React.FC<{
4345
formData.append("flagKey", props.flag.key);
4446
formData.append("flagStatus", pendingSwitchEnabled.toString());
4547

46-
fetch(`${keys.CLIENT_HOME_PAGE_URL}/api/flags/`, {
48+
doFetch<unknown, { message: string }>({
49+
url: `${keys.CLIENT_HOME_PAGE_URL}/api/flags/`,
4750
method: "PUT",
48-
credentials: "include",
49-
headers: {
50-
Accept: "application/json",
51-
"Access-Control-Allow-Credentials": "true",
52-
"Access-Control-Allow-Origin": "true",
53-
},
54-
body: formData,
55-
})
56-
.then(async (resp) => {
57-
return resp.json();
58-
})
59-
.then((respJson) => {
60-
if (respJson.success as boolean) {
61-
setEnabled(pendingSwitchEnabled);
62-
props.flag.status = pendingSwitchEnabled;
63-
setShowConfirmation(false);
64-
toast.success(`Flag status updated!`, {
65-
subMessage: `Flag: ${props.flag?.name}`,
66-
} as Toast);
67-
} else {
68-
let msg = "Generic error occurred, please try again.";
69-
if (respJson.errorCode === "error_input") {
70-
msg = "One or more required information are missing.";
71-
} else if (respJson.errorCode === "error_alreadysaved") {
72-
msg = "There is already a flag with the same name.";
73-
}
74-
console.log(msg);
51+
reqBody: formData,
52+
onError: (respJson) => {
53+
let msg = "Generic error occurred, please try again.";
54+
if (respJson.message === "error_input") {
55+
msg = "One or more required information are missing.";
56+
} else if (respJson.message === "error_alreadysaved") {
57+
msg = "There is already a flag with the same name.";
7558
}
76-
})
77-
.catch((error) => {
78-
console.log(error);
79-
});
59+
toast.error(msg);
60+
},
61+
onSuccess: () => {
62+
setEnabled(pendingSwitchEnabled);
63+
props.flag.status = pendingSwitchEnabled;
64+
setShowConfirmation(false);
65+
toast.success(`Flag status updated!`, {
66+
subMessage: `Flag: ${props.flag?.name}`,
67+
} as Toast);
68+
},
69+
});
8070
};
8171

8272
const cancelFlagUpdate = (): void => {

packages/ui/src/components/flags/RulesBoard.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { generateGuid } from "../../helpers/entityHelper";
55
import { SegmentModel } from "../../models/SegmentModel";
66
import { useEffect, useState } from "react";
77
import * as keys from "../../config/keys";
8+
import { useFetch } from "../../hooks/useFetch";
89

910
export type RulesBoardProps = {
1011
toEditRule?: RuleModel;
@@ -14,6 +15,7 @@ export type RulesBoardProps = {
1415
export const RulesBoard: React.FC<RulesBoardProps> = (props) => {
1516
const [selectedSegment, setSelectedSegment] = useState<SegmentModel>();
1617
const [segments, setSegments] = useState<SegmentModel[]>([]);
18+
const { doFetch } = useFetch();
1719

1820
useEffect(() => {
1921
if (props.toEditRule) {
@@ -22,23 +24,11 @@ export const RulesBoard: React.FC<RulesBoardProps> = (props) => {
2224
}, [props.toEditRule]);
2325

2426
useEffect(() => {
25-
fetch(`${keys.CLIENT_HOME_PAGE_URL}/api/segments/`, {
26-
method: "GET",
27-
credentials: "include",
28-
headers: {
29-
Accept: "application/json",
30-
"Content-Type": "application/json",
31-
"Access-Control-Allow-Credentials": "true",
32-
"Access-Control-Allow-Origin": "true",
33-
},
34-
})
35-
.then(async (resp) => {
36-
return resp.json();
37-
})
38-
.then((respJson) => {
27+
doFetch<SegmentModel[], unknown>({
28+
onSuccess: (fetchResp: SegmentModel[]) => {
3929
setSegments([]);
4030
const allSegments: SegmentModel[] = [];
41-
respJson.data.forEach((item: SegmentModel) => {
31+
fetchResp.forEach((item: SegmentModel) => {
4232
allSegments.push({
4333
name: item.name,
4434
description: item.description,
@@ -52,11 +42,12 @@ export const RulesBoard: React.FC<RulesBoardProps> = (props) => {
5242

5343
setSegments(allSegments);
5444
setSelectedSegment(allSegments[0]);
55-
})
56-
.catch((ex) => {
57-
console.log(ex);
58-
});
59-
}, []);
45+
},
46+
onError: () => {},
47+
url: `${keys.CLIENT_HOME_PAGE_URL}/api/segments/`,
48+
method: "GET",
49+
});
50+
}, [doFetch]);
6051

6152
const addRule = () => {
6253
if (!selectedSegment) {

0 commit comments

Comments
 (0)