Skip to content

Commit

Permalink
Merge pull request #6 from rootstrap/feature/add-interceptors
Browse files Browse the repository at this point in the history
Feature: Add Axios interceptors
  • Loading branch information
juanchoperezj authored Jul 17, 2024
2 parents 5e39be5 + b6bf808 commit 8f607db
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/api/common/client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Env } from '@env';
import axios from 'axios';

export const client = axios.create({
baseURL: Env.API_URL,
});
21 changes: 21 additions & 0 deletions src/api/common/interceptors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AxiosError, InternalAxiosRequestConfig } from 'axios';

import { client } from './client';
import { toCamelCase, toSnakeCase } from './utils';

export default function interceptors() {
client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.data = toSnakeCase(config.data);
return config;
});

client.interceptors.response.use(
(response) => {
response.data = toCamelCase(response.data);
return response;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
}
59 changes: 59 additions & 0 deletions src/api/common/utils.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { toCamelCase, toSnakeCase } from './utils';

describe('utils', () => {
describe('toCamelCase', () => {
it('should convert snake_case to camelCase', () => {
const obj = {
foo_bar: 'foo',
bar_baz: 'bar',
};
const expected = {
fooBar: 'foo',
barBaz: 'bar',
};
expect(toCamelCase(obj)).toEqual(expected);
});

it('should convert to camelCase only snake_case keys', () => {
const obj = {
foo_bar: 'foo',
bar_baz: 'bar',
fooBar: 'foo',
barBaz: 'bar',
};
const expected = {
fooBar: 'foo',
barBaz: 'bar',
};
expect(toCamelCase(obj)).toEqual(expected);
});
});

describe('toSnakeCase', () => {
it('should convert camelCase to snake_case', () => {
const obj = {
fooBar: 'foo',
barBaz: 'bar',
};
const expected = {
foo_bar: 'foo',
bar_baz: 'bar',
};
expect(toSnakeCase(obj)).toEqual(expected);
});

it('should convert to snake_case only camelCase keys', () => {
const obj = {
fooBar: 'foo',
barBaz: 'bar',
foo_bar: 'foo',
bar_baz: 'bar',
};
const expected = {
foo_bar: 'foo',
bar_baz: 'bar',
};
expect(toSnakeCase(obj)).toEqual(expected);
});
});
});
32 changes: 32 additions & 0 deletions src/api/common/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,35 @@ export const getNextPageParam: GetPreviousPageParamFunction<
unknown,
PaginateQuery<unknown>
> = (page) => getUrlParameters(page.next)?.offset ?? null;

type GenericObject = { [key: string]: any };

export const toCamelCase = (obj: GenericObject): GenericObject => {
const newObj: GenericObject = {};
for (const key in obj) {
if (key.includes('_')) {
const newKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
newObj[newKey] = obj[key];
} else {
newObj[key] = obj[key];
}
}
return newObj;
};

export const toSnakeCase = (obj: GenericObject): GenericObject => {
const newObj: GenericObject = {};
for (const key in obj) {
let newKey = key.match(/([A-Z])/g)
? key
.match(/([A-Z])/g)!
.reduce(
(str, c) => str.replace(new RegExp(c), '_' + c.toLowerCase()),
key
)
: key;
newKey = newKey.substring(key.slice(0, 1).match(/([A-Z])/g) ? 1 : 0);
newObj[newKey] = obj[key];
}
return newObj;
};
2 changes: 2 additions & 0 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FlashMessage from 'react-native-flash-message';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

import { APIProvider } from '@/api';
import interceptors from '@/api/common/interceptors';
import { hydrateAuth, loadSelectedTheme } from '@/core';
import { useThemeConfig } from '@/core/use-theme-config';

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

hydrateAuth();
loadSelectedTheme();
interceptors();
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();

Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/items-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const ItemsContainer = ({ children, title }: Props) => {
<>
{title && <Text className="pb-2 pt-4 text-lg" tx={title} />}
{
<View className=" rounded-md border-[1px] border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
<View className=" rounded-md border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
{children}
</View>
}
Expand Down
6 changes: 3 additions & 3 deletions src/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import { Text } from './text';
const selectTv = tv({
slots: {
container: 'mb-4',
label: 'text-grey-100 dark:text-neutral-100 text-lg mb-1',
label: 'text-grey-100 mb-1 text-lg dark:text-neutral-100',
input:
'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',
'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',
inputValue: 'dark:text-neutral-100',
},

Expand Down Expand Up @@ -123,7 +123,7 @@ const Option = React.memo(
}) => {
return (
<Pressable
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"
className="flex-row items-center border-b border-neutral-300 bg-white px-3 py-2 dark:border-neutral-700 dark:bg-neutral-800"
{...props}
>
<Text className="flex-1 dark:text-neutral-100 ">{label}</Text>
Expand Down

0 comments on commit 8f607db

Please sign in to comment.