diff --git a/api/api-error-plugin.ts b/api/api-error-plugin.ts
new file mode 100644
index 0000000..073827a
--- /dev/null
+++ b/api/api-error-plugin.ts
@@ -0,0 +1,22 @@
+import { ZodiosPlugin } from '@zodios/core';
+import { AxiosError } from 'axios';
+
+const SKIP_ERROR_HANDLING_URLS = ['/example/skip-error-handling'];
+
+const errorErrorPlugin: ZodiosPlugin = {
+ name: 'errorErrorPlugin',
+ error: async (api, config, err) => {
+ if (SKIP_ERROR_HANDLING_URLS.includes(config.url)) {
+ console.log('Skipping error handling for', config.url);
+ throw err;
+ }
+
+ if (err instanceof AxiosError) {
+ console.error('AxiosError', err);
+ }
+
+ throw err;
+ },
+};
+
+export default errorErrorPlugin;
diff --git a/api/api.ts b/api/api.ts
new file mode 100644
index 0000000..692493e
--- /dev/null
+++ b/api/api.ts
@@ -0,0 +1,14 @@
+import { Zodios } from '@zodios/core';
+import { ZodiosHooks } from '@zodios/react';
+import apiErrorPlugin from './api-error-plugin';
+import exampleApi from './example';
+
+const API_URL = process.env.EXPO_PUBLIC_API_URL || '';
+
+// Zodios API client
+const apiClient = new Zodios(API_URL, [...exampleApi]);
+
+apiClient.use(apiErrorPlugin);
+const api = new ZodiosHooks('exampleApi', apiClient);
+
+export { api, apiClient };
diff --git a/api/example/index.ts b/api/example/index.ts
new file mode 100644
index 0000000..98a2c91
--- /dev/null
+++ b/api/example/index.ts
@@ -0,0 +1,47 @@
+import { apiBuilder } from '@zodios/core';
+import { z } from 'zod';
+
+// Endpoints for Example API - Example Endpoints.
+const exampleApi = apiBuilder({
+ method: 'get',
+ path: '/example',
+ alias: 'getExample',
+ description: 'Get example',
+ response: z.object({
+ text: z.string(),
+ }),
+ parameters: [
+ {
+ type: 'Query',
+ name: 'name',
+ description: 'User name',
+ schema: z.string().optional(),
+ },
+ ],
+ errors: [{ status: 'default', schema: z.object({ message: z.string() }) }],
+})
+ .addEndpoint({
+ method: 'post',
+ path: '/example/:exampleId',
+ description: 'Add example',
+ alias: 'addExample',
+ response: z.object({}),
+ parameters: [
+ {
+ name: 'exampleId',
+ type: 'Path',
+ schema: z.string(),
+ },
+ {
+ name: 'body',
+ type: 'Body',
+ schema: z.object({
+ name: z.string(),
+ }),
+ },
+ ],
+ errors: [{ status: 'default', schema: z.object({ message: z.string() }) }],
+ })
+ .build();
+
+export default exampleApi;
diff --git a/app/(authenticated)/index.tsx b/app/(authenticated)/index.tsx
index 2bbcd14..117d990 100644
--- a/app/(authenticated)/index.tsx
+++ b/app/(authenticated)/index.tsx
@@ -1,7 +1,10 @@
+import { useExampleStore } from '@utils/stores/example-store';
import { router } from 'expo-router';
import { Button, Text, View } from 'react-native';
const AuthHomeScreen = () => {
+ const { value, increment, decrement } = useExampleStore();
+
return (
{
alignItems: 'center',
}}>
Authenticated Home
+ {value}
+
);
diff --git a/app/_layout.tsx b/app/_layout.tsx
index b279575..47d04a7 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -4,40 +4,33 @@
// environment: process.env.EXPO_PUBLIC_SENTRY_ENV,
// });
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import useCustomFonts from '@utils/hooks/use-custom-fonts';
import '@utils/i18n/config';
-import { FontSource, useFonts } from 'expo-font';
import { Slot, SplashScreen } from 'expo-router';
-import { useEffect } from 'react';
import '../global.css';
+const queryClient = new QueryClient();
+
// Keep splash screen visible while we fetch fonts and other assets
SplashScreen.preventAutoHideAsync();
const RootLayout = () => {
- const [loaded] = useFonts({
- thin: require('../assets/fonts/Inter-Thin.ttf') as FontSource,
- light: require('../assets/fonts/Inter-Light.ttf') as FontSource,
- regular: require('../assets/fonts/Inter-Regular.ttf') as FontSource,
- medium: require('../assets/fonts/Inter-Medium.ttf') as FontSource,
- bold: require('../assets/fonts/Inter-Bold.ttf') as FontSource,
- black: require('../assets/fonts/Inter-Black.ttf') as FontSource,
- semibold: require('../assets/fonts/Inter-SemiBold.ttf') as FontSource,
- extrabold: require('../assets/fonts/Inter-ExtraBold.ttf') as FontSource,
- extralight: require('../assets/fonts/Inter-ExtraLight.ttf') as FontSource,
+ const [fontsLoaded, fontError] = useCustomFonts({
+ callback: async () => {
+ await SplashScreen.hideAsync();
+ },
});
- useEffect(() => {
- if (loaded) {
- // Hide splash when fonts are loaded
- SplashScreen.hideAsync();
- }
- }, [loaded]);
-
- if (!loaded) {
+ if (!fontsLoaded && !fontError) {
return null;
}
- return ;
+ return (
+
+
+
+ );
};
export default RootLayout;
diff --git a/package.json b/package.json
index da02396..0fde3d9 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,10 @@
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.23.1",
+ "@tanstack/react-query": "^5.29.2",
+ "@zodios/core": "^10.9.6",
+ "@zodios/react": "^10.4.5",
+ "axios": "^1.6.8",
"expo": "~50.0.14",
"expo-constants": "~15.4.5",
"expo-font": "~11.10.3",
@@ -24,18 +28,21 @@
"i18next": "^23.10.1",
"intl-pluralrules": "^2.0.1",
"nativewind": "^4.0.1",
- "react": "18.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"react-i18next": "^14.1.0",
"react-native": "0.73.6",
"react-native-reanimated": "~3.6.2",
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"tailwindcss": "^3.4.3",
+ "zod": "^3.22.4",
"zustand": "^4.5.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
- "@types/react": "^18.2.77",
+ "@types/react": "^18.2.78",
+ "@types/react-dom": "^18.2.25",
"@types/react-native": "^0.73.0",
"eslint": "8",
"eslint-config-universe": "^12.0.0",
diff --git a/tsconfig.json b/tsconfig.json
index c6d9fc4..4f194fe 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,20 +4,11 @@
"strict": true,
"baseUrl": ".",
"paths": {
- "@components/*": [
- "components/*"
- ],
- "@features/*": [
- "features/*"
- ],
- "@utils/*": [
- "utils/*"
- ]
+ "@assets/*": ["assets/*"],
+ "@components/*": ["components/*"],
+ "@features/*": ["features/*"],
+ "@utils/*": ["utils/*"]
}
},
- "include": [
- "**/*.ts",
- "**/*.tsx",
- "**/*.js"
- ]
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js"]
}
diff --git a/utils/hooks/use-custom-fonts.ts b/utils/hooks/use-custom-fonts.ts
new file mode 100644
index 0000000..174f023
--- /dev/null
+++ b/utils/hooks/use-custom-fonts.ts
@@ -0,0 +1,40 @@
+import { FontSource, loadAsync } from 'expo-font';
+import { useEffect, useState } from 'react';
+
+const fontsToLoad = {
+ thin: require('@assets/fonts/Inter-Thin.ttf'),
+ light: require('@assets/fonts/Inter-Light.ttf'),
+ regular: require('@assets/fonts/Inter-Regular.ttf'),
+ medium: require('@assets/fonts/Inter-Medium.ttf'),
+ bold: require('@assets/fonts/Inter-Bold.ttf'),
+ black: require('@assets/fonts/Inter-Black.ttf'),
+ semibold: require('@assets/fonts/Inter-SemiBold.ttf'),
+ extrabold: require('@assets/fonts/Inter-ExtraBold.ttf'),
+ extralight: require('@assets/fonts/Inter-ExtraLight.ttf'),
+} as Record;
+
+export default function useSfFonts({
+ callback,
+}: {
+ callback?: () => void;
+}): [boolean, Error | undefined] {
+ const [loaded, setLoaded] = useState(false);
+ const [error, setError] = useState();
+
+ useEffect(() => {
+ loadAsync(fontsToLoad)
+ .then(() => {
+ // Change the state.
+ setLoaded(true);
+
+ // Call the parent's callback.
+ callback?.();
+ })
+ .catch((e) => {
+ setError(e);
+ console.error('Fonts not loaded.', e);
+ });
+ }, [callback]);
+
+ return [loaded, error];
+}
diff --git a/yarn.lock b/yarn.lock
index 8e7e093..c99be48 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2295,6 +2295,18 @@
dependencies:
"@sinonjs/commons" "^3.0.0"
+"@tanstack/query-core@5.29.0":
+ version "5.29.0"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.29.0.tgz#d0b3d12c07d5a47f42ab0c1ed4f317106f3d4b20"
+ integrity sha512-WgPTRs58hm9CMzEr5jpISe8HXa3qKQ8CxewdYZeVnA54JrPY9B1CZiwsCoLpLkf0dGRZq+LcX5OiJb0bEsOFww==
+
+"@tanstack/react-query@^5.29.2":
+ version "5.29.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.29.2.tgz#c55ffbfaf9d8cf34212428db2b6c61ca6b545188"
+ integrity sha512-nyuWILR4u7H5moLGSiifLh8kIqQDLNOHGuSz0rcp+J75fNc8aQLyr5+I2JCHU3n+nJrTTW1ssgAD8HiKD7IFBQ==
+ dependencies:
+ "@tanstack/query-core" "5.29.0"
+
"@types/cookie@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
@@ -2341,6 +2353,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
+"@types/react-dom@^18.2.25":
+ version "18.2.25"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.25.tgz#2946a30081f53e7c8d585eb138277245caedc521"
+ integrity sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-native@^0.73.0":
version "0.73.0"
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.73.0.tgz#b316be230745779814caa533360262140b0f5984"
@@ -2348,10 +2367,10 @@
dependencies:
react-native "*"
-"@types/react@^18.2.77":
- version "18.2.77"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.77.tgz#af2f857b6a6dfb6ca89ec102ebc147b1f1616880"
- integrity sha512-CUT9KUUF+HytDM7WiXKLF9qUSg4tGImwy4FXTlfEDPEkkNUzJ7rVFolYweJ9fS1ljoIaP7M7Rdjc5eUm/Yu5AA==
+"@types/react@*", "@types/react@^18.2.78":
+ version "18.2.78"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.78.tgz#94aec453d0ccca909998a2b4b2fd78af15a7d2fe"
+ integrity sha512-qOwdPnnitQY4xKlKayt42q5W5UQrSHjgoXNVEtxeqdITJ99k4VXJOP3vt8Rkm9HmgJpH50UNU+rlqfkfWOqp0A==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
@@ -2515,6 +2534,16 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3"
integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==
+"@zodios/core@^10.9.6":
+ version "10.9.6"
+ resolved "https://registry.yarnpkg.com/@zodios/core/-/core-10.9.6.tgz#64ad831216e6ffa71679ea6be8d1ed882bb04d83"
+ integrity sha512-aH4rOdb3AcezN7ws8vDgBfGboZMk2JGGzEq/DtW65MhnRxyTGRuLJRWVQ/2KxDgWvV2F5oTkAS+5pnjKbl0n+A==
+
+"@zodios/react@^10.4.5":
+ version "10.4.5"
+ resolved "https://registry.yarnpkg.com/@zodios/react/-/react-10.4.5.tgz#e796152ee034f24facc5c8a156d98c81c43eaec5"
+ integrity sha512-2jisuquf30bEQg6KzhtYnoutXEQD9uD/I0vBO3d3AzL91SLhFTeHNSk2MwkBj0LL/wCIWt9v0sUL52OMv++EwQ==
+
"@zxing/text-encoding@0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b"
@@ -2837,6 +2866,15 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
+axios@^1.6.8:
+ version "1.6.8"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
+ integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
babel-core@^7.0.0-bridge.0:
version "7.0.0-bridge.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
@@ -4516,6 +4554,11 @@ flow-parser@^0.206.0:
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.206.0.tgz#f4f794f8026535278393308e01ea72f31000bfef"
integrity sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==
+follow-redirects@^1.15.6:
+ version "1.15.6"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
+ integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
+
fontfaceobserver@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz#5fb392116e75d5024b7ec8e4f2ce92106d1488c8"
@@ -4545,6 +4588,15 @@ form-data@^3.0.1:
combined-stream "^1.0.8"
mime-types "^2.1.12"
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
freeport-async@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/freeport-async/-/freeport-async-2.0.0.tgz#6adf2ec0c629d11abff92836acd04b399135bab4"
@@ -6957,6 +7009,11 @@ prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
@@ -7020,6 +7077,14 @@ react-devtools-core@^4.27.7:
shell-quote "^1.6.1"
ws "^7"
+react-dom@^18.2.0:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+ integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.0"
+
react-fast-compare@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
@@ -7155,7 +7220,7 @@ react-shallow-renderer@^16.15.0:
object-assign "^4.1.1"
react-is "^16.12.0 || ^17.0.0 || ^18.0.0"
-react@18.2.0:
+react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -7470,6 +7535,13 @@ scheduler@0.24.0-canary-efb381bbf-20230505:
dependencies:
loose-envify "^1.1.0"
+scheduler@^0.23.0:
+ version "0.23.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
+ integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
+ dependencies:
+ loose-envify "^1.1.0"
+
schema-utils@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"
@@ -8794,6 +8866,11 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+zod@^3.22.4:
+ version "3.22.4"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
+ integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
+
zustand@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848"