diff --git a/blog/.env.example b/blog/.env.example new file mode 100644 index 0000000..08bfcb8 --- /dev/null +++ b/blog/.env.example @@ -0,0 +1,2 @@ +CMS_API_URL=cms.minisource.ir +CMS_API_TOKEN=8715fba146f2a041cf1bb74e1a84105700a10e1961987e737c3a1f062231443a335684100310f7118fa3819d210df5ac363917449d2f414d2b4110324bc22e390f1ca3868a911654f0f6ad59801849658ecbcd2f36e5a3e223413ab6ba0c4b7ddb13c3ad5f9c4b3f366f37e0cdd25aeaee0708c6dc23842a0ee950579ee9f6e1 \ No newline at end of file diff --git a/blog/package-lock.json b/blog/package-lock.json index 8405fc7..e926879 100644 --- a/blog/package-lock.json +++ b/blog/package-lock.json @@ -8,15 +8,19 @@ "name": "blog", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.90.2", + "axios": "^1.12.2", "next": "15.5.4", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "react-hot-toast": "^2.6.0", + "recoil": "^0.7.7" }, "devDependencies": { "@tailwindcss/postcss": "^4", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^19.1.15", + "@types/react-dom": "^19.1.9", "tailwindcss": "^4", "typescript": "^5" } @@ -954,6 +958,32 @@ "tailwindcss": "4.1.13" } }, + "node_modules/@tanstack/query-core": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", + "integrity": "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz", + "integrity": "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/node": { "version": "20.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", @@ -965,9 +995,9 @@ } }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.1.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.15.tgz", + "integrity": "sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA==", "dev": true, "license": "MIT", "dependencies": { @@ -984,6 +1014,36 @@ "@types/react": "^19.0.0" } }, + "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.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "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/caniuse-lite": { "version": "1.0.30001745", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", @@ -1020,13 +1080,33 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "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/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, + "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/detect-libc": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", @@ -1037,6 +1117,20 @@ "node": ">=8" } }, + "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/enhanced-resolve": { "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", @@ -1051,6 +1145,154 @@ "node": ">=10.13.0" } }, + "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", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "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", @@ -1058,6 +1300,51 @@ "dev": true, "license": "ISC" }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==", + "license": "MIT" + }, + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jiti": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", @@ -1317,6 +1604,36 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "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/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/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/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -1473,6 +1790,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -1494,6 +1817,43 @@ "react": "^19.1.0" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "license": "MIT", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", diff --git a/blog/package.json b/blog/package.json index daa9daa..70b924f 100644 --- a/blog/package.json +++ b/blog/package.json @@ -8,16 +8,20 @@ "start": "next start" }, "dependencies": { + "@tanstack/react-query": "^5.90.2", + "axios": "^1.12.2", + "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", - "next": "15.5.4" + "react-hot-toast": "^2.6.0", + "recoil": "^0.7.7" }, "devDependencies": { - "typescript": "^5", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", "@tailwindcss/postcss": "^4", - "tailwindcss": "^4" + "@types/node": "^20", + "@types/react": "^19.1.15", + "@types/react-dom": "^19.1.9", + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/blog/src/apis/api.d.ts b/blog/src/apis/api.d.ts new file mode 100644 index 0000000..f0e7a18 --- /dev/null +++ b/blog/src/apis/api.d.ts @@ -0,0 +1,29 @@ +type TResponse = { + data: T; + meta?: { + pagination?: { + page: number; + pageSize: number; + pageCount: number; + total: number; + }; + }; +}; + +type TListApi = { + page?: number; + pageSize?: number; +}; + +type TApi = Promise<{ + data: T; + pagination?: { + page: number; + pageSize: number; + pageCount: number; + total: number; + }; + isSuccess: boolean; + statusCode: number; + message?: string; +}>; diff --git a/blog/src/apis/article/index.ts b/blog/src/apis/article/index.ts new file mode 100644 index 0000000..8df66e9 --- /dev/null +++ b/blog/src/apis/article/index.ts @@ -0,0 +1,16 @@ +import { AxiosInstance } from "axios"; +import Api from "../base"; +import { TArticle } from "../../schema/article/article.type"; + +export default class ArticleApis extends Api { + constructor(BASE_URL: string, axios: AxiosInstance) { + super(BASE_URL, axios); + } + + // گرفتن لیست اسناد از Strapi + GetArticles = async (): TApi => { + return await this.axios + .get(`/api/articles`) + .then((response) => response.data); + }; +} diff --git a/blog/src/apis/base/index.ts b/blog/src/apis/base/index.ts new file mode 100644 index 0000000..cf61eca --- /dev/null +++ b/blog/src/apis/base/index.ts @@ -0,0 +1,11 @@ +import { AxiosInstance } from "axios"; + +export default class Api { + protected BASE_URL: string; + protected axios: AxiosInstance; + + constructor(BASE_URL: string, axios: AxiosInstance) { + this.BASE_URL = BASE_URL; + this.axios = axios; + } +} diff --git a/blog/src/apis/constant.ts b/blog/src/apis/constant.ts new file mode 100644 index 0000000..92a1ef1 --- /dev/null +++ b/blog/src/apis/constant.ts @@ -0,0 +1,7 @@ +const CACHE_TIME = { + SHORT: 5 * 60 * 1000, //5 minutes + NORMAL: 60 * 60 * 1000, //1hours + LONG: 24 * 60 * 60 * 1000, //24hours +}; + +export { CACHE_TIME }; diff --git a/blog/src/apis/page/index.ts b/blog/src/apis/page/index.ts new file mode 100644 index 0000000..e3c55d4 --- /dev/null +++ b/blog/src/apis/page/index.ts @@ -0,0 +1,19 @@ +import { AxiosInstance } from "axios"; +import Api from "../base"; +import { TPageData } from "@/schema/page/page.type"; + +export default class PageApis extends Api { + constructor(BASE_URL: string, axios: AxiosInstance) { + super(BASE_URL, axios); + } + + getStaticPage = async (slug: string): TApi => { + return await this.axios + .get(`/api/pages`, { + params: { + "filters[slug][$eq]": slug, + }, + }) + .then((response) => response.data.data); + }; +} diff --git a/blog/src/app/[...dynamic]/page.tsx b/blog/src/app/[...dynamic]/page.tsx new file mode 100644 index 0000000..5789abf --- /dev/null +++ b/blog/src/app/[...dynamic]/page.tsx @@ -0,0 +1,14 @@ +"use client"; +import { useStaticPage } from "./use-static-page"; + +const StaticPage = (props: any) => { + const { data, isSuccess, isLoading, error, isError } = useStaticPage( + props.params.dynamic.join("/") + ); + + return ( + <> + ); +} + +export default StaticPage; diff --git a/blog/src/app/[...dynamic]/use-static-page.tsx b/blog/src/app/[...dynamic]/use-static-page.tsx new file mode 100644 index 0000000..00553f6 --- /dev/null +++ b/blog/src/app/[...dynamic]/use-static-page.tsx @@ -0,0 +1,22 @@ + +import { useApi } from "../hooks/use-api"; +import PageApis from "@/apis/page"; +import Axios from "@/utils/axios"; + +export const useStaticPage = (slug: string) => { + const KEY = ["StaticPage", slug]; + const pageApi = new PageApis("", Axios()); + const { data, error, isSuccess, refetch, isLoading, isError } = useApi( + KEY, + () => pageApi.getStaticPage(slug) + ); + + return { + data: isSuccess ? data.data : null, + isLoading, + isSuccess, + refetch, + isError, + error, + }; +}; diff --git a/blog/src/app/article/.gitkeep b/blog/src/app/article/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/blog/src/app/hooks/use-api/index.tsx b/blog/src/app/hooks/use-api/index.tsx new file mode 100644 index 0000000..e364809 --- /dev/null +++ b/blog/src/app/hooks/use-api/index.tsx @@ -0,0 +1,86 @@ +import { AxiosError } from "axios"; +import { + useQuery, + QueryKey, + useMutation, + useQueryClient, + useInfiniteQuery, +} from "@tanstack/react-query"; +import { + TUseMutateApiFunction, + TUseMutateApiOptions, + TUseApi, + TUseApiFunction, + TUseApiOptions, + TUseMutateApi, + TUseInfiniteApiFunction, + TUseInfiniteApiOptions, + TUseInfiniteApi, +} from "./use-api.type"; +import useErrorHandler from "../use-error-handler"; +import { CACHE_TIME } from "@/apis/constant"; + +export const useApi = ( + key: QueryKey, + fn: TUseApiFunction, + options?: TUseApiOptions +): TUseApi => { + return useQuery, AxiosError>({ + queryKey: key, + queryFn: fn, + ...options, + staleTime: CACHE_TIME.SHORT, + gcTime: CACHE_TIME.NORMAL, + retry: 1, + refetchOnWindowFocus: false, + }); +}; + +export const useInfiniteApi = ( + key: QueryKey, + fn: TUseInfiniteApiFunction, + options?: TUseInfiniteApiOptions +) => { + //TUseInfiniteApi + // return useInfiniteQuery, AxiosError, any>({ + // queryKey: key, + // queryFn: fn, + // staleTime: CACHE_TIME.SHORT, + // gcTime: CACHE_TIME.NORMAL, + // retry: 1, + // refetchOnWindowFocus: false, + // ...options, + // }); +}; + +export const useMutateApi = ( + fn: TUseMutateApiFunction, + resetKeys?: QueryKey, + options?: TUseMutateApiOptions, + withErrorHandler?: boolean +): TUseMutateApi => { + const client = useQueryClient(); + const { handleError } = useErrorHandler(); + + return useMutation, AxiosError, TVariables, any>({ + mutationKey: resetKeys, + mutationFn: fn, + // onError: (error, variables, context) => { + // options?.onError?.(error, variables, context); + // if (withErrorHandler === undefined || withErrorHandler === true) + // handleError(error); + // }, + // onSettled: (data: any, error: any, variables: any, context: any) => { + // options?.onSettled?.(data, error, variables, context); + // if (resetKeys) { + // if (Array.isArray(resetKeys[0])) + // resetKeys.forEach((key: any) => + // client.invalidateQueries({ queryKey: key }) + // ); + // else client.invalidateQueries({ queryKey: resetKeys }); + // } + // }, + retry: false, + ...options, + }); +}; diff --git a/blog/src/app/hooks/use-api/use-api.type.ts b/blog/src/app/hooks/use-api/use-api.type.ts new file mode 100644 index 0000000..785adeb --- /dev/null +++ b/blog/src/app/hooks/use-api/use-api.type.ts @@ -0,0 +1,50 @@ +import { + // ContextOptions, + MutationFunction, + QueryFunction, + QueryKey, + UseInfiniteQueryOptions, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, + UseInfiniteQueryResult +} from "@tanstack/react-query"; +import { AxiosError } from "axios"; + +export type TUseApiFunction = QueryFunction>; +export type TUseApiOptions = Omit< + UseQueryOptions, AxiosError, TResponse, QueryKey>, + "queryKey" | "queryFn" +>; +export type TUseApi = UseQueryResult, Error>; + +export type TUseMutateApiFunction = MutationFunction< + TResponse, + TVariables +>; +export type TUseMutateApiOptions = UseMutationOptions< + TResponse, + AxiosError, + TVariables +>; +export type TUseMutateApi = UseMutationResult< + TResponse, + Error, + TVariables + // ContextOptions //fix this not in @tanstack/react-query +>; + +export type TUseInfiniteApiFunction = QueryFunction>; +export type TUseInfiniteApiOptions = Omit< + UseInfiniteQueryOptions< + TResponse, + AxiosError, + TResponse, + any, + QueryKey + >, + "queryKey" | "queryFn" +>; + +export type TUseInfiniteApi = UseInfiniteQueryResult, Error> diff --git a/blog/src/app/hooks/use-error-handler/index.ts b/blog/src/app/hooks/use-error-handler/index.ts new file mode 100644 index 0000000..e89c6b9 --- /dev/null +++ b/blog/src/app/hooks/use-error-handler/index.ts @@ -0,0 +1,35 @@ +import { AxiosError } from "axios"; +import { toast } from "react-hot-toast"; + +const useErrorHandler = () => { + const handleError = (error: AxiosError) => { + if (error.response?.status === 500) + toast.error("خطای 0 - با تیم پشتیبانی تماس حاصل فرمایید."); + else if (error.response?.status === 404) + toast.error( + "خطای 1 - " + + `${ + error.response.data.message + ? error.response.data.message + : "با تیم پشتیبانی تماس حاصل فرمایید" + }` + ); + else if (error.response?.status === 405) + toast.error("خطای 3 - پروتکل انتخابی اشتباه است."); + else if (error.response?.status === 403) toast.error("عدم دسترسی"); + else if (error.response?.status === 429) + toast.error("لطفا کمی صبرکنید و دوباره تلاش کنید!"); + else if (error.response?.status === 400) + toast.error("خطای 2 - " + error.response.data.message); + else if (error.response?.status === 401 || error.code === "401") { + // null + } + }; + return { handleError }; +}; + +export default useErrorHandler; + + +// else if (error.response?.status === 422) +// toast.error("خطای 4 - " + error.response.data.message); diff --git a/blog/src/app/layout.tsx b/blog/src/app/layout.tsx index f7fa87e..60b1fa2 100644 --- a/blog/src/app/layout.tsx +++ b/blog/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { React } from "next/dist/server/route-modules/app-page/vendored/rsc/entrypoints"; const geistSans = Geist({ variable: "--font-geist-sans", diff --git a/blog/src/app/not-found.tsx b/blog/src/app/not-found.tsx new file mode 100644 index 0000000..6f4c3b5 --- /dev/null +++ b/blog/src/app/not-found.tsx @@ -0,0 +1,5 @@ +const NotFound = () => { + return
page not found
; +}; + +export default NotFound; diff --git a/blog/src/atoms/atoms.d.ts b/blog/src/atoms/atoms.d.ts new file mode 100644 index 0000000..ff8b4c5 --- /dev/null +++ b/blog/src/atoms/atoms.d.ts @@ -0,0 +1 @@ +export default {}; diff --git a/blog/src/atoms/index.ts b/blog/src/atoms/index.ts new file mode 100644 index 0000000..1a24269 --- /dev/null +++ b/blog/src/atoms/index.ts @@ -0,0 +1,38 @@ +import { atom } from "recoil"; + +// const localStorageEffect = +// (key :any) => +// ({ setSelf, onSet }) => { +// const savedValue = +// typeof window !== "undefined" ? localStorage.getItem(key) : null; +// if (savedValue != null) { +// setSelf(JSON.parse(savedValue)); +// } + +// onSet((newValue, _, isReset) => { +// isReset +// ? typeof window !== "undefined" && localStorage.removeItem(key) +// : typeof window !== "undefined" && +// localStorage.setItem(key, JSON.stringify(newValue)); +// }); +// }; + +// const authAtom = atom<{ +// optTokenTime: string; +// phone: string; +// email: string; +// fullName: string; +// isAuth: boolean; +// authStep: "validation" | "email" | "otp" | "forget"; +// }>({ +// key: "authAtom", +// default: { +// optTokenTime: "", +// phone: "", +// email: "", +// fullName: "", +// isAuth: false, +// authStep: "validation", +// }, +// effects: [localStorageEffect("auth")], +// }); \ No newline at end of file diff --git a/blog/src/components/button/button.type.ts b/blog/src/components/button/button.type.ts new file mode 100644 index 0000000..3022b7d --- /dev/null +++ b/blog/src/components/button/button.type.ts @@ -0,0 +1,11 @@ +import { React } from "next/dist/server/route-modules/app-page/vendored/rsc/entrypoints"; +import { JSX } from "react/jsx-runtime"; + +export type TButtonProps = { + variant: "contained" | "outlined" | "icon" | "text"; + endIcon?: JSX.Element; //use icon name in svg component + startIcon?: JSX.Element; //use icon name in svg component + isLoading?: boolean; +} & React.ComponentProps<"button">; + +// note we can use ButtonHTMLAttributes diff --git a/blog/src/components/button/index.tsx b/blog/src/components/button/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/blog/src/schema/article/article.type.ts b/blog/src/schema/article/article.type.ts new file mode 100644 index 0000000..cfb663a --- /dev/null +++ b/blog/src/schema/article/article.type.ts @@ -0,0 +1,19 @@ +export type TArticle = { + id: number; + documentId: string; + title: string; + content: string; + slug?: string; + coverImage?: { + id: number; + url: string; + alternativeText?: string; + }; + author?: { + id: number; + username: string; + }; + publishedAt: string; + createdAt: string; + updatedAt: string; +}; \ No newline at end of file diff --git a/blog/src/schema/page/page.type.ts b/blog/src/schema/page/page.type.ts new file mode 100644 index 0000000..44094b0 --- /dev/null +++ b/blog/src/schema/page/page.type.ts @@ -0,0 +1,15 @@ +export type TPageData = { + id: number; + documentId: string; + title: string; + slug: string; + content: string; + publishedAt: string; + createdAt: string; + updatedAt: string; + coverImage?: { + id: number; + url: string; + alternativeText?: string; + }; +}; diff --git a/blog/src/utils/axios.ts b/blog/src/utils/axios.ts new file mode 100644 index 0000000..37d0c2c --- /dev/null +++ b/blog/src/utils/axios.ts @@ -0,0 +1,37 @@ +"use client"; +import axios, { AxiosInstance } from "axios"; + +let CustomAxios: AxiosInstance; + +const Axios = (): AxiosInstance => { + if (axios.defaults) { + CustomAxios = axios.create({ + withCredentials: false, + baseURL: + process.env.CMS_API_URL || "http://localhost:1337", + }); + + CustomAxios.interceptors.request.use( + async (config) => { + const token = process.env.CMS_API_TOKEN; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + (error) => Promise.reject(error) + ); + + CustomAxios.interceptors.response.use( + (response) => response, + (error) => Promise.reject(error) + ); + } else { + CustomAxios = axios; + } + + return CustomAxios; +}; + +export { CustomAxios }; +export default Axios; diff --git a/blog/src/utils/index.ts b/blog/src/utils/index.ts new file mode 100644 index 0000000..0704116 --- /dev/null +++ b/blog/src/utils/index.ts @@ -0,0 +1 @@ +export * from "./axios";