Skip to content

Commit 8f607db

Browse files
Merge pull request #6 from rootstrap/feature/add-interceptors
Feature: Add Axios interceptors
2 parents 5e39be5 + b6bf808 commit 8f607db

File tree

7 files changed

+119
-4
lines changed

7 files changed

+119
-4
lines changed

src/api/common/client.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Env } from '@env';
22
import axios from 'axios';
3+
34
export const client = axios.create({
45
baseURL: Env.API_URL,
56
});

src/api/common/interceptors.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { AxiosError, InternalAxiosRequestConfig } from 'axios';
2+
3+
import { client } from './client';
4+
import { toCamelCase, toSnakeCase } from './utils';
5+
6+
export default function interceptors() {
7+
client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
8+
config.data = toSnakeCase(config.data);
9+
return config;
10+
});
11+
12+
client.interceptors.response.use(
13+
(response) => {
14+
response.data = toCamelCase(response.data);
15+
return response;
16+
},
17+
(error: AxiosError) => {
18+
return Promise.reject(error);
19+
}
20+
);
21+
}

src/api/common/utils.spec.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { toCamelCase, toSnakeCase } from './utils';
2+
3+
describe('utils', () => {
4+
describe('toCamelCase', () => {
5+
it('should convert snake_case to camelCase', () => {
6+
const obj = {
7+
foo_bar: 'foo',
8+
bar_baz: 'bar',
9+
};
10+
const expected = {
11+
fooBar: 'foo',
12+
barBaz: 'bar',
13+
};
14+
expect(toCamelCase(obj)).toEqual(expected);
15+
});
16+
17+
it('should convert to camelCase only snake_case keys', () => {
18+
const obj = {
19+
foo_bar: 'foo',
20+
bar_baz: 'bar',
21+
fooBar: 'foo',
22+
barBaz: 'bar',
23+
};
24+
const expected = {
25+
fooBar: 'foo',
26+
barBaz: 'bar',
27+
};
28+
expect(toCamelCase(obj)).toEqual(expected);
29+
});
30+
});
31+
32+
describe('toSnakeCase', () => {
33+
it('should convert camelCase to snake_case', () => {
34+
const obj = {
35+
fooBar: 'foo',
36+
barBaz: 'bar',
37+
};
38+
const expected = {
39+
foo_bar: 'foo',
40+
bar_baz: 'bar',
41+
};
42+
expect(toSnakeCase(obj)).toEqual(expected);
43+
});
44+
45+
it('should convert to snake_case only camelCase keys', () => {
46+
const obj = {
47+
fooBar: 'foo',
48+
barBaz: 'bar',
49+
foo_bar: 'foo',
50+
bar_baz: 'bar',
51+
};
52+
const expected = {
53+
foo_bar: 'foo',
54+
bar_baz: 'bar',
55+
};
56+
expect(toSnakeCase(obj)).toEqual(expected);
57+
});
58+
});
59+
});

src/api/common/utils.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,35 @@ export const getNextPageParam: GetPreviousPageParamFunction<
4949
unknown,
5050
PaginateQuery<unknown>
5151
> = (page) => getUrlParameters(page.next)?.offset ?? null;
52+
53+
type GenericObject = { [key: string]: any };
54+
55+
export const toCamelCase = (obj: GenericObject): GenericObject => {
56+
const newObj: GenericObject = {};
57+
for (const key in obj) {
58+
if (key.includes('_')) {
59+
const newKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
60+
newObj[newKey] = obj[key];
61+
} else {
62+
newObj[key] = obj[key];
63+
}
64+
}
65+
return newObj;
66+
};
67+
68+
export const toSnakeCase = (obj: GenericObject): GenericObject => {
69+
const newObj: GenericObject = {};
70+
for (const key in obj) {
71+
let newKey = key.match(/([A-Z])/g)
72+
? key
73+
.match(/([A-Z])/g)!
74+
.reduce(
75+
(str, c) => str.replace(new RegExp(c), '_' + c.toLowerCase()),
76+
key
77+
)
78+
: key;
79+
newKey = newKey.substring(key.slice(0, 1).match(/([A-Z])/g) ? 1 : 0);
80+
newObj[newKey] = obj[key];
81+
}
82+
return newObj;
83+
};

src/app/_layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import FlashMessage from 'react-native-flash-message';
88
import { GestureHandlerRootView } from 'react-native-gesture-handler';
99

1010
import { APIProvider } from '@/api';
11+
import interceptors from '@/api/common/interceptors';
1112
import { hydrateAuth, loadSelectedTheme } from '@/core';
1213
import { useThemeConfig } from '@/core/use-theme-config';
1314

@@ -22,6 +23,7 @@ export const unstable_settings = {
2223

2324
hydrateAuth();
2425
loadSelectedTheme();
26+
interceptors();
2527
// Prevent the splash screen from auto-hiding before asset loading is complete.
2628
SplashScreen.preventAutoHideAsync();
2729

src/components/settings/items-container.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const ItemsContainer = ({ children, title }: Props) => {
1313
<>
1414
{title && <Text className="pb-2 pt-4 text-lg" tx={title} />}
1515
{
16-
<View className=" rounded-md border-[1px] border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
16+
<View className=" rounded-md border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
1717
{children}
1818
</View>
1919
}

src/ui/select.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import { Text } from './text';
2525
const selectTv = tv({
2626
slots: {
2727
container: 'mb-4',
28-
label: 'text-grey-100 dark:text-neutral-100 text-lg mb-1',
28+
label: 'text-grey-100 mb-1 text-lg dark:text-neutral-100',
2929
input:
30-
'mt-0 flex-row items-center justify-center border-[0.5px] border-grey-50 px-3 py-3 rounded-xl dark:bg-neutral-800 dark:border-neutral-500',
30+
'border-grey-50 mt-0 flex-row items-center justify-center rounded-xl border-[0.5px] p-3 dark:border-neutral-500 dark:bg-neutral-800',
3131
inputValue: 'dark:text-neutral-100',
3232
},
3333

@@ -123,7 +123,7 @@ const Option = React.memo(
123123
}) => {
124124
return (
125125
<Pressable
126-
className="flex-row items-center border-b-[1px] border-neutral-300 bg-white px-3 py-2 dark:border-neutral-700 dark:bg-neutral-800"
126+
className="flex-row items-center border-b border-neutral-300 bg-white px-3 py-2 dark:border-neutral-700 dark:bg-neutral-800"
127127
{...props}
128128
>
129129
<Text className="flex-1 dark:text-neutral-100 ">{label}</Text>

0 commit comments

Comments
 (0)