diff --git a/package-lock.json b/package-lock.json index fee8320..8ac4890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@expo-google-fonts/jetbrains-mono": "^0.4.1", "@expo-google-fonts/space-grotesk": "^0.4.1", "@expo/ui": "~56.0.14", + "@tanstack/react-query": "^5.102.8", + "axios": "^1.20.0", "babel-preset-expo": "^57.0.9", "expo": "~56.0.5", "expo-blur": "~56.0.4", @@ -45,7 +47,8 @@ "react-native-web": "~0.21.0", "react-native-worklets": "0.8.3", "tailwindcss": "^3.4.19", - "zod": "^4.5.4" + "zod": "^4.5.4", + "zustand": "^5.0.15" }, "devDependencies": { "@types/react": "~19.2.2", @@ -2869,6 +2872,32 @@ "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", + "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", + "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.102.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -3283,6 +3312,49 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", @@ -3665,6 +3737,19 @@ "node": ">= 0.8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -3889,6 +3974,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4174,6 +4271,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4297,6 +4403,20 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4345,6 +4465,15 @@ "stackframe": "^1.3.4" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -4354,6 +4483,33 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5613,12 +5769,69 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", "license": "BSD-2-Clause" }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -5669,6 +5882,30 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -5678,6 +5915,19 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/getenv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", @@ -5716,6 +5966,18 @@ "node": ">=10.13.0" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5731,10 +5993,37 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6733,6 +7022,15 @@ "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", "license": "Apache-2.0" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -8088,6 +8386,15 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10173,6 +10480,35 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 7c9c882..c53c047 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "@expo-google-fonts/jetbrains-mono": "^0.4.1", "@expo-google-fonts/space-grotesk": "^0.4.1", "@expo/ui": "~56.0.14", + "@tanstack/react-query": "^5.102.8", + "axios": "^1.20.0", "babel-preset-expo": "^57.0.9", "expo": "~56.0.5", "expo-blur": "~56.0.4", @@ -40,7 +42,8 @@ "react-native-web": "~0.21.0", "react-native-worklets": "0.8.3", "tailwindcss": "^3.4.19", - "zod": "^4.5.4" + "zod": "^4.5.4", + "zustand": "^5.0.15" }, "devDependencies": { "@types/react": "~19.2.2", diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..c090386 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,11 @@ +import axios from 'axios'; + +import { API_BASE_URL } from './config'; + +export const apiClient = axios.create({ + baseURL: API_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + }, +}); diff --git a/src/api/config.ts b/src/api/config.ts new file mode 100644 index 0000000..f2abae1 --- /dev/null +++ b/src/api/config.ts @@ -0,0 +1,4 @@ +export const API_BASE_URL = process.env.EXPO_PUBLIC_API_BASE_URL ?? 'http://localhost:3000'; + +// Toggle once here to switch all API functions between mocks and real backend. +export const API_USE_MOCKS = (process.env.EXPO_PUBLIC_API_USE_MOCKS ?? 'true') === 'true'; diff --git a/src/api/entities/animators.ts b/src/api/entities/animators.ts new file mode 100644 index 0000000..c03b7bf --- /dev/null +++ b/src/api/entities/animators.ts @@ -0,0 +1,9 @@ +import type { Animator } from '@/models'; +import { mockAnimators } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const animatorsApi = createResourceApi({ + endpoint: 'animators', + mockData: mockAnimators, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/comment_likes.ts b/src/api/entities/comment_likes.ts new file mode 100644 index 0000000..b4d4eeb --- /dev/null +++ b/src/api/entities/comment_likes.ts @@ -0,0 +1,9 @@ +import { mockCommentLikes } from '@/mocks'; +import type { CommentLike } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const comment_likesApi = createResourceApi({ + endpoint: 'comment_likes', + mockData: mockCommentLikes, + resolveMockKey: (item) => `${item.user_id}:${item.comment_id}`, +}); diff --git a/src/api/entities/comments.ts b/src/api/entities/comments.ts new file mode 100644 index 0000000..b633aa4 --- /dev/null +++ b/src/api/entities/comments.ts @@ -0,0 +1,9 @@ +import type { Comment } from '@/models'; +import { mockComments } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const commentsApi = createResourceApi({ + endpoint: 'comments', + mockData: mockComments, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/event_external_links.ts b/src/api/entities/event_external_links.ts new file mode 100644 index 0000000..315adef --- /dev/null +++ b/src/api/entities/event_external_links.ts @@ -0,0 +1,9 @@ +import type { EventExternalLink } from '@/models'; +import { mockEventExternalLinks } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const event_external_linksApi = createResourceApi({ + endpoint: 'event_external_links', + mockData: mockEventExternalLinks, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/event_hashtags.ts b/src/api/entities/event_hashtags.ts new file mode 100644 index 0000000..70f61c6 --- /dev/null +++ b/src/api/entities/event_hashtags.ts @@ -0,0 +1,9 @@ +import { mockEventHashtags } from '@/mocks'; +import type { EventHashtag } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const event_hashtagsApi = createResourceApi({ + endpoint: 'event_hashtags', + mockData: mockEventHashtags, + resolveMockKey: (item) => `${item.event_id}:${item.hashtag_id}`, +}); diff --git a/src/api/entities/event_images.ts b/src/api/entities/event_images.ts new file mode 100644 index 0000000..0f394e8 --- /dev/null +++ b/src/api/entities/event_images.ts @@ -0,0 +1,9 @@ +import type { EventImage } from '@/models'; +import { mockEventImages } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const event_imagesApi = createResourceApi({ + endpoint: 'event_images', + mockData: mockEventImages, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/event_lineup.ts b/src/api/entities/event_lineup.ts new file mode 100644 index 0000000..fedeef6 --- /dev/null +++ b/src/api/entities/event_lineup.ts @@ -0,0 +1,9 @@ +import type { EventLineup } from '@/models'; +import { mockEventLineup } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const event_lineupApi = createResourceApi({ + endpoint: 'event_lineup', + mockData: mockEventLineup, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/event_stats_snapshots.ts b/src/api/entities/event_stats_snapshots.ts new file mode 100644 index 0000000..ebe1122 --- /dev/null +++ b/src/api/entities/event_stats_snapshots.ts @@ -0,0 +1,9 @@ +import type { EventStatsSnapshot } from '@/models'; +import { mockEventStatsSnapshots } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const event_stats_snapshotsApi = createResourceApi({ + endpoint: 'event_stats_snapshots', + mockData: mockEventStatsSnapshots, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/event_themes.ts b/src/api/entities/event_themes.ts new file mode 100644 index 0000000..d40c2a4 --- /dev/null +++ b/src/api/entities/event_themes.ts @@ -0,0 +1,9 @@ +import { mockEventThemes } from '@/mocks'; +import type { EventTheme } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const event_themesApi = createResourceApi({ + endpoint: 'event_themes', + mockData: mockEventThemes, + resolveMockKey: (item) => `${item.event_id}:${item.theme_id}`, +}); diff --git a/src/api/entities/events.ts b/src/api/entities/events.ts new file mode 100644 index 0000000..38269e2 --- /dev/null +++ b/src/api/entities/events.ts @@ -0,0 +1,9 @@ +import type { Event } from '@/models'; +import { mockEvents } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const eventsApi = createResourceApi({ + endpoint: 'events', + mockData: mockEvents, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/hashtags.ts b/src/api/entities/hashtags.ts new file mode 100644 index 0000000..7d63cb5 --- /dev/null +++ b/src/api/entities/hashtags.ts @@ -0,0 +1,9 @@ +import type { Hashtag } from '@/models'; +import { mockHashtags } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const hashtagsApi = createResourceApi({ + endpoint: 'hashtags', + mockData: mockHashtags, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/index.ts b/src/api/entities/index.ts new file mode 100644 index 0000000..2b888d8 --- /dev/null +++ b/src/api/entities/index.ts @@ -0,0 +1,29 @@ +export * from './animators'; +export * from './comment_likes'; +export * from './comments'; +export * from './event_external_links'; +export * from './event_hashtags'; +export * from './event_images'; +export * from './event_lineup'; +export * from './event_stats_snapshots'; +export * from './event_themes'; +export * from './events'; +export * from './hashtags'; +export * from './organizer_members'; +export * from './organizer_subscriptions'; +export * from './organizers'; +export * from './plans'; +export * from './post_likes'; +export * from './post_media'; +export * from './posts'; +export * from './search_history'; +export * from './social_auth_providers'; +export * from './themes'; +export * from './tickets'; +export * from './user_animator_follows'; +export * from './user_event_interactions'; +export * from './user_event_shares'; +export * from './user_organizer_follows'; +export * from './user_theme_preferences'; +export * from './users'; + diff --git a/src/api/entities/organizer_members.ts b/src/api/entities/organizer_members.ts new file mode 100644 index 0000000..834a832 --- /dev/null +++ b/src/api/entities/organizer_members.ts @@ -0,0 +1,9 @@ +import type { OrganizerMember } from '@/models'; +import { mockOrganizerMembers } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const organizer_membersApi = createResourceApi({ + endpoint: 'organizer_members', + mockData: mockOrganizerMembers, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/organizer_subscriptions.ts b/src/api/entities/organizer_subscriptions.ts new file mode 100644 index 0000000..2cbc968 --- /dev/null +++ b/src/api/entities/organizer_subscriptions.ts @@ -0,0 +1,9 @@ +import type { OrganizerSubscription } from '@/models'; +import { mockOrganizerSubscriptions } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const organizer_subscriptionsApi = createResourceApi({ + endpoint: 'organizer_subscriptions', + mockData: mockOrganizerSubscriptions, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/organizers.ts b/src/api/entities/organizers.ts new file mode 100644 index 0000000..196e3c7 --- /dev/null +++ b/src/api/entities/organizers.ts @@ -0,0 +1,9 @@ +import type { Organizer } from '@/models'; +import { mockOrganizers } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const organizersApi = createResourceApi({ + endpoint: 'organizers', + mockData: mockOrganizers, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/plans.ts b/src/api/entities/plans.ts new file mode 100644 index 0000000..a826cfe --- /dev/null +++ b/src/api/entities/plans.ts @@ -0,0 +1,9 @@ +import type { Plan } from '@/models'; +import { mockPlans } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const plansApi = createResourceApi({ + endpoint: 'plans', + mockData: mockPlans, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/post_likes.ts b/src/api/entities/post_likes.ts new file mode 100644 index 0000000..579003f --- /dev/null +++ b/src/api/entities/post_likes.ts @@ -0,0 +1,9 @@ +import { mockPostLikes } from '@/mocks'; +import type { PostLike } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const post_likesApi = createResourceApi({ + endpoint: 'post_likes', + mockData: mockPostLikes, + resolveMockKey: (item) => `${item.user_id}:${item.post_id}`, +}); diff --git a/src/api/entities/post_media.ts b/src/api/entities/post_media.ts new file mode 100644 index 0000000..7e70e89 --- /dev/null +++ b/src/api/entities/post_media.ts @@ -0,0 +1,9 @@ +import type { PostMedia } from '@/models'; +import { mockPostMedia } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const post_mediaApi = createResourceApi({ + endpoint: 'post_media', + mockData: mockPostMedia, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/posts.ts b/src/api/entities/posts.ts new file mode 100644 index 0000000..02429de --- /dev/null +++ b/src/api/entities/posts.ts @@ -0,0 +1,9 @@ +import type { Post } from '@/models'; +import { mockPosts } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const postsApi = createResourceApi({ + endpoint: 'posts', + mockData: mockPosts, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/search_history.ts b/src/api/entities/search_history.ts new file mode 100644 index 0000000..6a725cc --- /dev/null +++ b/src/api/entities/search_history.ts @@ -0,0 +1,9 @@ +import type { SearchHistory } from '@/models'; +import { mockSearchHistory } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const search_historyApi = createResourceApi({ + endpoint: 'search_history', + mockData: mockSearchHistory, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/social_auth_providers.ts b/src/api/entities/social_auth_providers.ts new file mode 100644 index 0000000..b47ecfb --- /dev/null +++ b/src/api/entities/social_auth_providers.ts @@ -0,0 +1,9 @@ +import type { SocialAuthProvider } from '@/models'; +import { mockSocialAuthProviders } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const social_auth_providersApi = createResourceApi({ + endpoint: 'social_auth_providers', + mockData: mockSocialAuthProviders, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/themes.ts b/src/api/entities/themes.ts new file mode 100644 index 0000000..3796e2c --- /dev/null +++ b/src/api/entities/themes.ts @@ -0,0 +1,9 @@ +import type { Theme } from '@/models'; +import { mockThemes } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const themesApi = createResourceApi({ + endpoint: 'themes', + mockData: mockThemes, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/tickets.ts b/src/api/entities/tickets.ts new file mode 100644 index 0000000..7bd6ec8 --- /dev/null +++ b/src/api/entities/tickets.ts @@ -0,0 +1,9 @@ +import type { Ticket } from '@/models'; +import { mockTickets } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const ticketsApi = createResourceApi({ + endpoint: 'tickets', + mockData: mockTickets, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/user_animator_follows.ts b/src/api/entities/user_animator_follows.ts new file mode 100644 index 0000000..6c9bdaf --- /dev/null +++ b/src/api/entities/user_animator_follows.ts @@ -0,0 +1,9 @@ +import { mockUserAnimatorFollows } from '@/mocks'; +import type { UserAnimatorFollow } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const user_animator_followsApi = createResourceApi({ + endpoint: 'user_animator_follows', + mockData: mockUserAnimatorFollows, + resolveMockKey: (item) => `${item.user_id}:${item.animator_id}`, +}); diff --git a/src/api/entities/user_event_interactions.ts b/src/api/entities/user_event_interactions.ts new file mode 100644 index 0000000..0166732 --- /dev/null +++ b/src/api/entities/user_event_interactions.ts @@ -0,0 +1,9 @@ +import { mockUserEventInteractions } from '@/mocks'; +import type { UserEventInteraction } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const user_event_interactionsApi = createResourceApi({ + endpoint: 'user_event_interactions', + mockData: mockUserEventInteractions, + resolveMockKey: (item) => `${item.user_id}:${item.event_id}`, +}); diff --git a/src/api/entities/user_event_shares.ts b/src/api/entities/user_event_shares.ts new file mode 100644 index 0000000..07a303e --- /dev/null +++ b/src/api/entities/user_event_shares.ts @@ -0,0 +1,9 @@ +import type { UserEventShare } from '@/models'; +import { mockUserEventShares } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const user_event_sharesApi = createResourceApi({ + endpoint: 'user_event_shares', + mockData: mockUserEventShares, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/entities/user_organizer_follows.ts b/src/api/entities/user_organizer_follows.ts new file mode 100644 index 0000000..570e8e9 --- /dev/null +++ b/src/api/entities/user_organizer_follows.ts @@ -0,0 +1,9 @@ +import { mockUserOrganizerFollows } from '@/mocks'; +import type { UserOrganizerFollow } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const user_organizer_followsApi = createResourceApi({ + endpoint: 'user_organizer_follows', + mockData: mockUserOrganizerFollows, + resolveMockKey: (item) => `${item.user_id}:${item.organizer_id}`, +}); diff --git a/src/api/entities/user_theme_preferences.ts b/src/api/entities/user_theme_preferences.ts new file mode 100644 index 0000000..e2f4031 --- /dev/null +++ b/src/api/entities/user_theme_preferences.ts @@ -0,0 +1,9 @@ +import { mockUserThemePreferences } from '@/mocks'; +import type { UserThemePreference } from '@/models'; +import { createResourceApi } from '../resourceFactory'; + +export const user_theme_preferencesApi = createResourceApi({ + endpoint: 'user_theme_preferences', + mockData: mockUserThemePreferences, + resolveMockKey: (item) => `${item.user_id}:${item.theme_id}`, +}); diff --git a/src/api/entities/users.ts b/src/api/entities/users.ts new file mode 100644 index 0000000..3a89bcc --- /dev/null +++ b/src/api/entities/users.ts @@ -0,0 +1,9 @@ +import type { User } from '@/models'; +import { mockUsers } from '@/mocks'; +import { createResourceApi } from '../resourceFactory'; + +export const usersApi = createResourceApi({ + endpoint: 'users', + mockData: mockUsers, + resolveMockKey: (item) => item.id, +}); diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..a186651 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,33 @@ +export * from './client'; +export * from './config'; +export * from './resourceFactory'; +export * from './types'; +export * from './entities/animators'; +export * from './entities/comment_likes'; +export * from './entities/comments'; +export * from './entities/event_external_links'; +export * from './entities/event_hashtags'; +export * from './entities/event_images'; +export * from './entities/event_lineup'; +export * from './entities/event_stats_snapshots'; +export * from './entities/event_themes'; +export * from './entities/events'; +export * from './entities/hashtags'; +export * from './entities/organizer_members'; +export * from './entities/organizer_subscriptions'; +export * from './entities/organizers'; +export * from './entities/plans'; +export * from './entities/post_likes'; +export * from './entities/post_media'; +export * from './entities/posts'; +export * from './entities/search_history'; +export * from './entities/social_auth_providers'; +export * from './entities/themes'; +export * from './entities/tickets'; +export * from './entities/user_animator_follows'; +export * from './entities/user_event_interactions'; +export * from './entities/user_event_shares'; +export * from './entities/user_organizer_follows'; +export * from './entities/user_theme_preferences'; +export * from './entities/users'; + diff --git a/src/api/mockStore.ts b/src/api/mockStore.ts new file mode 100644 index 0000000..d39532a --- /dev/null +++ b/src/api/mockStore.ts @@ -0,0 +1,90 @@ +type KeyResolver = (item: T) => string; + +type MockStoreOptions = { + data: T[]; + resolveKey: KeyResolver; +}; + +const clone = (value: T): T => { + if (typeof globalThis.structuredClone === 'function') { + return globalThis.structuredClone(value); + } + + return JSON.parse(JSON.stringify(value)) as T; +}; + +const createMockId = (): string => { + const random = Math.random().toString(16).slice(2, 10).padEnd(8, '0'); + return `00000000-0000-4000-8000-${random}`; +}; + +const ensureHasId = (payload: Partial): Partial => { + if ('id' in payload && typeof payload.id === 'string' && payload.id.length > 0) { + return payload; + } + + return { + ...payload, + id: createMockId(), + }; +}; + +export const createMockStore = (options: MockStoreOptions) => { + const { resolveKey } = options; + const records = [...options.data]; + + const findIndex = (id: string): number => records.findIndex((item) => resolveKey(item) === id); + + return { + list: async (): Promise => clone(records), + get: async (id: string): Promise => { + const index = findIndex(id); + if (index === -1) { + throw new Error(`Mock record not found for id "${id}"`); + } + + return clone(records[index]); + }, + create: async (payload: Partial): Promise => { + const next = ensureHasId(payload) as T; + records.push(next); + return clone(next); + }, + update: async (id: string, payload: Partial): Promise => { + const index = findIndex(id); + if (index === -1) { + throw new Error(`Mock record not found for id "${id}"`); + } + + const next = { + ...records[index], + ...payload, + }; + + records[index] = next; + return clone(next); + }, + patch: async (id: string, payload: Partial): Promise => { + const index = findIndex(id); + if (index === -1) { + throw new Error(`Mock record not found for id "${id}"`); + } + + const next = { + ...records[index], + ...payload, + }; + + records[index] = next; + return clone(next); + }, + remove: async (id: string): Promise => { + const index = findIndex(id); + if (index === -1) { + throw new Error(`Mock record not found for id "${id}"`); + } + + records.splice(index, 1); + }, + }; +}; diff --git a/src/api/resourceFactory.ts b/src/api/resourceFactory.ts new file mode 100644 index 0000000..31bf00e --- /dev/null +++ b/src/api/resourceFactory.ts @@ -0,0 +1,66 @@ +import type { AxiosInstance } from 'axios'; + +import { apiClient } from './client'; +import { API_USE_MOCKS } from './config'; +import { createMockStore } from './mockStore'; +import type { CreatePayload, CrudApi, UpdatePayload } from './types'; + +type CreateResourceApiOptions = { + endpoint: string; + mockData: T[]; + resolveMockKey: (item: T) => string; + client?: AxiosInstance; + useMocks?: boolean; +}; + +export const createResourceApi = (options: CreateResourceApiOptions): CrudApi => { + const { + endpoint, + mockData, + resolveMockKey, + client = apiClient, + useMocks = API_USE_MOCKS, + } = options; + + const mockStore = createMockStore({ + data: mockData, + resolveKey: resolveMockKey, + }); + + if (useMocks) { + return { + list: () => mockStore.list(), + get: (id) => mockStore.get(id), + create: (payload) => mockStore.create(payload), + update: (id, payload) => mockStore.update(id, payload), + patch: (id, payload) => mockStore.patch(id, payload), + remove: (id) => mockStore.remove(id), + }; + } + + return { + list: async () => { + const response = await client.get(`/${endpoint}`); + return response.data; + }, + get: async (id: string) => { + const response = await client.get(`/${endpoint}/${id}`); + return response.data; + }, + create: async (payload: CreatePayload) => { + const response = await client.post(`/${endpoint}`, payload); + return response.data; + }, + update: async (id: string, payload: UpdatePayload) => { + const response = await client.put(`/${endpoint}/${id}`, payload); + return response.data; + }, + patch: async (id: string, payload: UpdatePayload) => { + const response = await client.patch(`/${endpoint}/${id}`, payload); + return response.data; + }, + remove: async (id: string) => { + await client.delete(`/${endpoint}/${id}`); + }, + }; +}; diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..290f82e --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,13 @@ +export type EntityWithId = { id: string }; + +export type CreatePayload = Partial; +export type UpdatePayload = Partial; + +export type CrudApi = { + list: () => Promise; + get: (id: string) => Promise; + create: (payload: CreatePayload) => Promise; + update: (id: string, payload: UpdatePayload) => Promise; + patch: (id: string, payload: UpdatePayload) => Promise; + remove: (id: string) => Promise; +}; diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index ef58010..4ee59c7 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -1,5 +1,7 @@ +import { QueryClientProvider } from '@tanstack/react-query'; import { Stack } from 'expo-router'; +import { queryClient } from '../hooks/queryClient'; import { useAppFonts } from '../theme/typography'; export default function RootLayout() { @@ -10,6 +12,8 @@ export default function RootLayout() { } return ( - + + + ); } diff --git a/src/hooks/api/animators.ts b/src/hooks/api/animators.ts new file mode 100644 index 0000000..2601923 --- /dev/null +++ b/src/hooks/api/animators.ts @@ -0,0 +1,5 @@ +import type { Animator } from '@/models'; +import { animatorsApi } from '@/api/entities/animators'; +import { createCrudHooks } from './createCrudHooks'; + +export const animatorsHooks = createCrudHooks('animators', animatorsApi); diff --git a/src/hooks/api/comment_likes.ts b/src/hooks/api/comment_likes.ts new file mode 100644 index 0000000..110d68b --- /dev/null +++ b/src/hooks/api/comment_likes.ts @@ -0,0 +1,5 @@ +import type { CommentLike } from '@/models'; +import { comment_likesApi } from '@/api/entities/comment_likes'; +import { createCrudHooks } from './createCrudHooks'; + +export const comment_likesHooks = createCrudHooks('comment_likes', comment_likesApi); diff --git a/src/hooks/api/comments.ts b/src/hooks/api/comments.ts new file mode 100644 index 0000000..617f079 --- /dev/null +++ b/src/hooks/api/comments.ts @@ -0,0 +1,5 @@ +import type { Comment } from '@/models'; +import { commentsApi } from '@/api/entities/comments'; +import { createCrudHooks } from './createCrudHooks'; + +export const commentsHooks = createCrudHooks('comments', commentsApi); diff --git a/src/hooks/api/createCrudHooks.ts b/src/hooks/api/createCrudHooks.ts new file mode 100644 index 0000000..d8cf7b2 --- /dev/null +++ b/src/hooks/api/createCrudHooks.ts @@ -0,0 +1,102 @@ +import type { CrudApi } from '@/api/types'; +import { + useMutation, + useQuery, + useQueryClient, + type UseMutationOptions, + type UseQueryOptions, +} from '@tanstack/react-query'; + +type ResourceId = string; +type ListKey = [string, 'list']; +type DetailKey = [string, 'detail', ResourceId]; + +export const createCrudHooks = (resourceKey: string, api: CrudApi) => { + const listKey: ListKey = [resourceKey, 'list']; + + const useListQuery = (options?: Omit, 'queryKey' | 'queryFn'>) => + useQuery({ + queryKey: listKey, + queryFn: () => api.list(), + ...options, + }); + + const useDetailQuery = ( + id: ResourceId, + options?: Omit, 'queryKey' | 'queryFn'>, + ) => + useQuery({ + queryKey: [resourceKey, 'detail', id], + queryFn: () => api.get(id), + enabled: id !== undefined && id !== null && (options?.enabled ?? true), + ...options, + }); + + const useCreateMutation = ( + options?: UseMutationOptions>, + ) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (data) => api.create(data), + ...options, + onSuccess: (data, variables, onMutateResult, context) => { + queryClient.invalidateQueries({ queryKey: listKey }); + options?.onSuccess?.(data, variables, onMutateResult, context); + }, + }); + }; + + const useUpdateMutation = ( + options?: UseMutationOptions }>, + ) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, data }) => api.update(id, data), + ...options, + onSuccess: (data, variables, onMutateResult, context) => { + queryClient.invalidateQueries({ queryKey: listKey }); + queryClient.invalidateQueries({ queryKey: [resourceKey, 'detail', variables.id] }); + options?.onSuccess?.(data, variables, onMutateResult, context); + }, + }); + }; + + const usePatchMutation = ( + options?: UseMutationOptions }>, + ) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: ({ id, data }) => api.patch(id, data), + ...options, + onSuccess: (data, variables, onMutateResult, context) => { + queryClient.invalidateQueries({ queryKey: listKey }); + queryClient.invalidateQueries({ queryKey: [resourceKey, 'detail', variables.id] }); + options?.onSuccess?.(data, variables, onMutateResult, context); + }, + }); + }; + + const useDeleteMutation = ( + options?: UseMutationOptions, + ) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (id) => api.remove(id), + ...options, + onSuccess: (data, variables, onMutateResult, context) => { + queryClient.invalidateQueries({ queryKey: listKey }); + queryClient.invalidateQueries({ queryKey: [resourceKey, 'detail', variables] }); + options?.onSuccess?.(data, variables, onMutateResult, context); + }, + }); + }; + + return { + useListQuery, + useDetailQuery, + useCreateMutation, + useUpdateMutation, + usePatchMutation, + useDeleteMutation, + }; +}; diff --git a/src/hooks/api/event_external_links.ts b/src/hooks/api/event_external_links.ts new file mode 100644 index 0000000..b36bc12 --- /dev/null +++ b/src/hooks/api/event_external_links.ts @@ -0,0 +1,5 @@ +import type { EventExternalLink } from '@/models'; +import { event_external_linksApi } from '@/api/entities/event_external_links'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_external_linksHooks = createCrudHooks('event_external_links', event_external_linksApi); diff --git a/src/hooks/api/event_hashtags.ts b/src/hooks/api/event_hashtags.ts new file mode 100644 index 0000000..65cb2b5 --- /dev/null +++ b/src/hooks/api/event_hashtags.ts @@ -0,0 +1,5 @@ +import type { EventHashtag } from '@/models'; +import { event_hashtagsApi } from '@/api/entities/event_hashtags'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_hashtagsHooks = createCrudHooks('event_hashtags', event_hashtagsApi); diff --git a/src/hooks/api/event_images.ts b/src/hooks/api/event_images.ts new file mode 100644 index 0000000..94c976b --- /dev/null +++ b/src/hooks/api/event_images.ts @@ -0,0 +1,5 @@ +import type { EventImage } from '@/models'; +import { event_imagesApi } from '@/api/entities/event_images'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_imagesHooks = createCrudHooks('event_images', event_imagesApi); diff --git a/src/hooks/api/event_lineup.ts b/src/hooks/api/event_lineup.ts new file mode 100644 index 0000000..acb1650 --- /dev/null +++ b/src/hooks/api/event_lineup.ts @@ -0,0 +1,5 @@ +import type { EventLineup } from '@/models'; +import { event_lineupApi } from '@/api/entities/event_lineup'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_lineupHooks = createCrudHooks('event_lineup', event_lineupApi); diff --git a/src/hooks/api/event_stats_snapshots.ts b/src/hooks/api/event_stats_snapshots.ts new file mode 100644 index 0000000..e78b5db --- /dev/null +++ b/src/hooks/api/event_stats_snapshots.ts @@ -0,0 +1,5 @@ +import type { EventStatsSnapshot } from '@/models'; +import { event_stats_snapshotsApi } from '@/api/entities/event_stats_snapshots'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_stats_snapshotsHooks = createCrudHooks('event_stats_snapshots', event_stats_snapshotsApi); diff --git a/src/hooks/api/event_themes.ts b/src/hooks/api/event_themes.ts new file mode 100644 index 0000000..d03a0d7 --- /dev/null +++ b/src/hooks/api/event_themes.ts @@ -0,0 +1,5 @@ +import type { EventTheme } from '@/models'; +import { event_themesApi } from '@/api/entities/event_themes'; +import { createCrudHooks } from './createCrudHooks'; + +export const event_themesHooks = createCrudHooks('event_themes', event_themesApi); diff --git a/src/hooks/api/events.ts b/src/hooks/api/events.ts new file mode 100644 index 0000000..ade53ba --- /dev/null +++ b/src/hooks/api/events.ts @@ -0,0 +1,5 @@ +import type { Event } from '@/models'; +import { eventsApi } from '@/api/entities/events'; +import { createCrudHooks } from './createCrudHooks'; + +export const eventsHooks = createCrudHooks('events', eventsApi); diff --git a/src/hooks/api/hashtags.ts b/src/hooks/api/hashtags.ts new file mode 100644 index 0000000..2932a22 --- /dev/null +++ b/src/hooks/api/hashtags.ts @@ -0,0 +1,5 @@ +import type { Hashtag } from '@/models'; +import { hashtagsApi } from '@/api/entities/hashtags'; +import { createCrudHooks } from './createCrudHooks'; + +export const hashtagsHooks = createCrudHooks('hashtags', hashtagsApi); diff --git a/src/hooks/api/index.ts b/src/hooks/api/index.ts new file mode 100644 index 0000000..ebbc5e5 --- /dev/null +++ b/src/hooks/api/index.ts @@ -0,0 +1,30 @@ +export * from './createCrudHooks'; +export * from './animators'; +export * from './comment_likes'; +export * from './comments'; +export * from './event_external_links'; +export * from './event_hashtags'; +export * from './event_images'; +export * from './event_lineup'; +export * from './event_stats_snapshots'; +export * from './event_themes'; +export * from './events'; +export * from './hashtags'; +export * from './organizer_members'; +export * from './organizer_subscriptions'; +export * from './organizers'; +export * from './plans'; +export * from './post_likes'; +export * from './post_media'; +export * from './posts'; +export * from './search_history'; +export * from './social_auth_providers'; +export * from './themes'; +export * from './tickets'; +export * from './user_animator_follows'; +export * from './user_event_interactions'; +export * from './user_event_shares'; +export * from './user_organizer_follows'; +export * from './user_theme_preferences'; +export * from './users'; + diff --git a/src/hooks/api/organizer_members.ts b/src/hooks/api/organizer_members.ts new file mode 100644 index 0000000..0c0f225 --- /dev/null +++ b/src/hooks/api/organizer_members.ts @@ -0,0 +1,5 @@ +import type { OrganizerMember } from '@/models'; +import { organizer_membersApi } from '@/api/entities/organizer_members'; +import { createCrudHooks } from './createCrudHooks'; + +export const organizer_membersHooks = createCrudHooks('organizer_members', organizer_membersApi); diff --git a/src/hooks/api/organizer_subscriptions.ts b/src/hooks/api/organizer_subscriptions.ts new file mode 100644 index 0000000..76a676f --- /dev/null +++ b/src/hooks/api/organizer_subscriptions.ts @@ -0,0 +1,5 @@ +import type { OrganizerSubscription } from '@/models'; +import { organizer_subscriptionsApi } from '@/api/entities/organizer_subscriptions'; +import { createCrudHooks } from './createCrudHooks'; + +export const organizer_subscriptionsHooks = createCrudHooks('organizer_subscriptions', organizer_subscriptionsApi); diff --git a/src/hooks/api/organizers.ts b/src/hooks/api/organizers.ts new file mode 100644 index 0000000..93cf633 --- /dev/null +++ b/src/hooks/api/organizers.ts @@ -0,0 +1,5 @@ +import type { Organizer } from '@/models'; +import { organizersApi } from '@/api/entities/organizers'; +import { createCrudHooks } from './createCrudHooks'; + +export const organizersHooks = createCrudHooks('organizers', organizersApi); diff --git a/src/hooks/api/plans.ts b/src/hooks/api/plans.ts new file mode 100644 index 0000000..0d64123 --- /dev/null +++ b/src/hooks/api/plans.ts @@ -0,0 +1,5 @@ +import type { Plan } from '@/models'; +import { plansApi } from '@/api/entities/plans'; +import { createCrudHooks } from './createCrudHooks'; + +export const plansHooks = createCrudHooks('plans', plansApi); diff --git a/src/hooks/api/post_likes.ts b/src/hooks/api/post_likes.ts new file mode 100644 index 0000000..f553dc3 --- /dev/null +++ b/src/hooks/api/post_likes.ts @@ -0,0 +1,5 @@ +import type { PostLike } from '@/models'; +import { post_likesApi } from '@/api/entities/post_likes'; +import { createCrudHooks } from './createCrudHooks'; + +export const post_likesHooks = createCrudHooks('post_likes', post_likesApi); diff --git a/src/hooks/api/post_media.ts b/src/hooks/api/post_media.ts new file mode 100644 index 0000000..4292b4d --- /dev/null +++ b/src/hooks/api/post_media.ts @@ -0,0 +1,5 @@ +import type { PostMedia } from '@/models'; +import { post_mediaApi } from '@/api/entities/post_media'; +import { createCrudHooks } from './createCrudHooks'; + +export const post_mediaHooks = createCrudHooks('post_media', post_mediaApi); diff --git a/src/hooks/api/posts.ts b/src/hooks/api/posts.ts new file mode 100644 index 0000000..07a0e31 --- /dev/null +++ b/src/hooks/api/posts.ts @@ -0,0 +1,5 @@ +import type { Post } from '@/models'; +import { postsApi } from '@/api/entities/posts'; +import { createCrudHooks } from './createCrudHooks'; + +export const postsHooks = createCrudHooks('posts', postsApi); diff --git a/src/hooks/api/search_history.ts b/src/hooks/api/search_history.ts new file mode 100644 index 0000000..70cdca8 --- /dev/null +++ b/src/hooks/api/search_history.ts @@ -0,0 +1,5 @@ +import type { SearchHistory } from '@/models'; +import { search_historyApi } from '@/api/entities/search_history'; +import { createCrudHooks } from './createCrudHooks'; + +export const search_historyHooks = createCrudHooks('search_history', search_historyApi); diff --git a/src/hooks/api/social_auth_providers.ts b/src/hooks/api/social_auth_providers.ts new file mode 100644 index 0000000..a25c8fd --- /dev/null +++ b/src/hooks/api/social_auth_providers.ts @@ -0,0 +1,5 @@ +import type { SocialAuthProvider } from '@/models'; +import { social_auth_providersApi } from '@/api/entities/social_auth_providers'; +import { createCrudHooks } from './createCrudHooks'; + +export const social_auth_providersHooks = createCrudHooks('social_auth_providers', social_auth_providersApi); diff --git a/src/hooks/api/themes.ts b/src/hooks/api/themes.ts new file mode 100644 index 0000000..2768267 --- /dev/null +++ b/src/hooks/api/themes.ts @@ -0,0 +1,5 @@ +import type { Theme } from '@/models'; +import { themesApi } from '@/api/entities/themes'; +import { createCrudHooks } from './createCrudHooks'; + +export const themesHooks = createCrudHooks('themes', themesApi); diff --git a/src/hooks/api/tickets.ts b/src/hooks/api/tickets.ts new file mode 100644 index 0000000..e1982b6 --- /dev/null +++ b/src/hooks/api/tickets.ts @@ -0,0 +1,5 @@ +import type { Ticket } from '@/models'; +import { ticketsApi } from '@/api/entities/tickets'; +import { createCrudHooks } from './createCrudHooks'; + +export const ticketsHooks = createCrudHooks('tickets', ticketsApi); diff --git a/src/hooks/api/user_animator_follows.ts b/src/hooks/api/user_animator_follows.ts new file mode 100644 index 0000000..ac706bb --- /dev/null +++ b/src/hooks/api/user_animator_follows.ts @@ -0,0 +1,5 @@ +import type { UserAnimatorFollow } from '@/models'; +import { user_animator_followsApi } from '@/api/entities/user_animator_follows'; +import { createCrudHooks } from './createCrudHooks'; + +export const user_animator_followsHooks = createCrudHooks('user_animator_follows', user_animator_followsApi); diff --git a/src/hooks/api/user_event_interactions.ts b/src/hooks/api/user_event_interactions.ts new file mode 100644 index 0000000..d77a25f --- /dev/null +++ b/src/hooks/api/user_event_interactions.ts @@ -0,0 +1,5 @@ +import type { UserEventInteraction } from '@/models'; +import { user_event_interactionsApi } from '@/api/entities/user_event_interactions'; +import { createCrudHooks } from './createCrudHooks'; + +export const user_event_interactionsHooks = createCrudHooks('user_event_interactions', user_event_interactionsApi); diff --git a/src/hooks/api/user_event_shares.ts b/src/hooks/api/user_event_shares.ts new file mode 100644 index 0000000..e03789c --- /dev/null +++ b/src/hooks/api/user_event_shares.ts @@ -0,0 +1,5 @@ +import type { UserEventShare } from '@/models'; +import { user_event_sharesApi } from '@/api/entities/user_event_shares'; +import { createCrudHooks } from './createCrudHooks'; + +export const user_event_sharesHooks = createCrudHooks('user_event_shares', user_event_sharesApi); diff --git a/src/hooks/api/user_organizer_follows.ts b/src/hooks/api/user_organizer_follows.ts new file mode 100644 index 0000000..926b74d --- /dev/null +++ b/src/hooks/api/user_organizer_follows.ts @@ -0,0 +1,5 @@ +import type { UserOrganizerFollow } from '@/models'; +import { user_organizer_followsApi } from '@/api/entities/user_organizer_follows'; +import { createCrudHooks } from './createCrudHooks'; + +export const user_organizer_followsHooks = createCrudHooks('user_organizer_follows', user_organizer_followsApi); diff --git a/src/hooks/api/user_theme_preferences.ts b/src/hooks/api/user_theme_preferences.ts new file mode 100644 index 0000000..1f51b75 --- /dev/null +++ b/src/hooks/api/user_theme_preferences.ts @@ -0,0 +1,5 @@ +import type { UserThemePreference } from '@/models'; +import { user_theme_preferencesApi } from '@/api/entities/user_theme_preferences'; +import { createCrudHooks } from './createCrudHooks'; + +export const user_theme_preferencesHooks = createCrudHooks('user_theme_preferences', user_theme_preferencesApi); diff --git a/src/hooks/api/users.ts b/src/hooks/api/users.ts new file mode 100644 index 0000000..7e6ebe9 --- /dev/null +++ b/src/hooks/api/users.ts @@ -0,0 +1,5 @@ +import type { User } from '@/models'; +import { usersApi } from '@/api/entities/users'; +import { createCrudHooks } from './createCrudHooks'; + +export const usersHooks = createCrudHooks('users', usersApi); diff --git a/src/hooks/queryClient.ts b/src/hooks/queryClient.ts new file mode 100644 index 0000000..4ff82bc --- /dev/null +++ b/src/hooks/queryClient.ts @@ -0,0 +1,15 @@ +import { QueryClient } from '@tanstack/react-query'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + gcTime: 5 * 60_000, + refetchOnWindowFocus: false, + retry: 1, + }, + mutations: { + retry: 0, + }, + }, +});