From 84cedc1855a0b4e7a4322deed153b036fcbe2928 Mon Sep 17 00:00:00 2001 From: yogiedigital Date: Thu, 4 Dec 2025 20:21:32 +0700 Subject: [PATCH] add a page --- .gitignore | 1 + env.example | 1 + package-lock.json | 257 +++++++++++++++++++++++++++ package.json | 1 + src/App.jsx | 50 ++++-- src/Utils/Auth/Logout.jsx | 0 src/Utils/Auth/Signin.jsx | 0 src/components/Global/ApiRequest.jsx | 171 ++++++++++++++++++ src/pages/Example.jsx | 107 +++++++++++ src/pages/TheChild.jsx | 7 + 10 files changed, 578 insertions(+), 17 deletions(-) create mode 100644 env.example create mode 100644 src/Utils/Auth/Logout.jsx create mode 100644 src/Utils/Auth/Signin.jsx create mode 100644 src/components/Global/ApiRequest.jsx create mode 100644 src/pages/Example.jsx create mode 100644 src/pages/TheChild.jsx diff --git a/.gitignore b/.gitignore index a547bf3..438657a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/env.example b/env.example new file mode 100644 index 0000000..2587cc3 --- /dev/null +++ b/env.example @@ -0,0 +1 @@ +VITE_API_SERVER= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f454ed1..8e579e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "antd": "^5.25.2", + "axios": "^1.13.2", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -1672,6 +1673,21 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1720,6 +1736,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "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==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1788,6 +1816,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -1860,12 +1899,74 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "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==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.155", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", "dev": true }, + "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==", + "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==", + "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==", + "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==", + "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/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -2174,6 +2275,40 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "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" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "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/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2188,6 +2323,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2197,6 +2340,41 @@ "node": ">=6.9.0" } }, + "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==", + "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==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2221,6 +2399,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2230,6 +2419,42 @@ "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==", + "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==", + "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==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2411,6 +2636,33 @@ "yallist": "^3.0.2" } }, + "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==", + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2591,6 +2843,11 @@ "node": ">= 0.8.0" } }, + "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==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 33098b5..210fe5d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "antd": "^5.25.2", + "axios": "^1.13.2", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/src/App.jsx b/src/App.jsx index 716b3ca..b4790c7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,8 @@ import React, { useState } from 'react'; import { AppstoreOutlined, MailOutlined, SettingOutlined, CloseOutlined } from '@ant-design/icons'; import { Menu } from 'antd'; import { Radio, Tabs } from 'antd'; +import TheChild from './pages/TheChild'; +import Example from './pages/Example'; const items = [ { key: 'Dashboard', @@ -17,6 +19,7 @@ const items = [ { key: 'Devices', label: 'Devices' }, { key: 'Tags', label: 'Tags' }, { key: 'Products', label: 'Products' }, + { key: 'Example', label: 'Example' }, ], }, { @@ -74,7 +77,8 @@ const getLevelKeys = items1 => { return key; }; const levelKeys = getLevelKeys(items); -const TheChild = () => {return(
Hahalo
)} +// const TheChild = () => {return(
Hahalo
)} +const size = 'small'; const App = () => { // const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']); const [stateOpenKeys, setStateOpenKeys] = useState(['0']); @@ -97,7 +101,7 @@ const App = () => { // children: , // }, ]); - const [size, setSize] = useState('small'); + // const [size, setSize] = useState('small'); const [activeKey, setActiveKey] = useState('Dashboard'); const onOpenChange = openKeys => { @@ -132,17 +136,29 @@ const App = () => { } setItemsTab(newItems); }; + const add = (key) => { // const newKey = String((items || []).length + 1); // const newKey = String((itemsTab || []).length + 1); // console.log("newKey", newKey) // console.log("items", items) // console.log("itemsTab before", itemsTab) + + // console.log("key", key) + + let page; + if(key[0] === "Example") { + page = + } else { + page = `Content of editable tab ${key[0]}`; + } + let newtab = { // label: `Tab ${newKey}`, label: `${key[0]}`, key: key[0], - children: `Content of editable tab ${key[0]}`, + // children: `Content of editable tab ${key[0]}`, + children: page, } if (!itemsTab.find(item => item.key === newtab.key)) { @@ -159,7 +175,7 @@ const App = () => { }; const onAddTab = (targetKey, action) => { if (action === 'add') { - add(key); + // add(key); // console.log("Plus plus") } else { // console.log("targetKey", targetKey) @@ -170,19 +186,19 @@ const App = () => { // setSize(e.target.value); // }; - const renderTabBar = (props, DefaultTabBar) => { - return ( - - {(node) => { - // Filter out the add button node - if (node.key && node.key.toString().startsWith('rc-tabs-add')) { - return null; - } - return node; - }} - - ); - }; + // const renderTabBar = (props, DefaultTabBar) => { + // return ( + // + // {(node) => { + // // Filter out the add button node + // if (node.key && node.key.toString().startsWith('rc-tabs-add')) { + // return null; + // } + // return node; + // }} + // + // ); + // }; return ( diff --git a/src/Utils/Auth/Logout.jsx b/src/Utils/Auth/Logout.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/Utils/Auth/Signin.jsx b/src/Utils/Auth/Signin.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/Global/ApiRequest.jsx b/src/components/Global/ApiRequest.jsx new file mode 100644 index 0000000..cf166cf --- /dev/null +++ b/src/components/Global/ApiRequest.jsx @@ -0,0 +1,171 @@ +import axios from 'axios'; +import Swal from 'sweetalert2'; + +const baseURL = import.meta.env.VITE_API_SERVER; + +const instance = axios.create({ + baseURL, + withCredentials: true, +}); + +// axios khusus refresh +const refreshApi = axios.create({ + baseURL, + withCredentials: true, +}); + +instance.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + console.error('🚨 Response Error Interceptor:', { + status: error.response?.status, + url: originalRequest.url, + message: error.response?.data?.message, + hasRetried: originalRequest._retry, + }); + + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + console.log('🔄 Refresh token dipanggil...'); + const refreshRes = await refreshApi.post('/auth/refresh-token'); + + const newAccessToken = refreshRes.data.data.accessToken; + localStorage.setItem('token', newAccessToken); + console.log('✅ Token refreshed successfully'); + + // update token di header + instance.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`; + originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`; + + console.log('🔁 Retrying original request...'); + return instance(originalRequest); + } catch (refreshError) { + console.error( + '❌ Refresh token gagal:', + refreshError.response?.data || refreshError.message + ); + localStorage.clear(); + window.location.href = '/signin'; + } + } + + return Promise.reject(error); + } +); + +async function ApiRequest({ method = 'GET', params = {}, prefix = '/', token = true } = {}) { + const isFormData = params instanceof FormData; + + const request = { + method, + url: prefix, + data: params, + headers: { + 'Accept-Language': 'en_US', + ...(isFormData ? {} : { 'Content-Type': 'application/json' }), + }, + }; + + const rawToken = localStorage.getItem('token'); + if (token && rawToken) { + const cleanToken = rawToken.replace(/"/g, ''); + request.headers['Authorization'] = `Bearer ${cleanToken}`; + console.log('🔐 Sending request with token:', cleanToken.substring(0, 20) + '...'); + } else { + console.warn('⚠️ No token found in localStorage'); + } + + console.log('📤 API Request:', { method, url: prefix, hasToken: !!rawToken }); + + try { + const response = await instance(request); + console.log('✅ API Response:', { + url: prefix, + status: response.status, + statusCode: response.data?.statusCode, + }); + return { ...response, error: false }; + } catch (error) { + const status = error?.response?.status || 500; + const message = error?.response?.data?.message || error.message || 'Something Wrong'; + console.error('❌ API Error:', { + url: prefix, + status, + message, + fullError: error?.response?.data, + }); + + if (status !== 401) { + await cekError(status, message); + } + + return { ...error.response, error: true }; + } +} + +async function cekError(status, message = '') { + if (status === 403) { + await Swal.fire({ + icon: 'warning', + title: 'Forbidden', + text: message, + }); + } else if (status >= 500) { + await Swal.fire({ + icon: 'error', + title: 'Server Error', + text: message, + }); + } else { + await Swal.fire({ + icon: 'warning', + title: 'Peringatan', + text: message, + }); + } +} + +const SendRequest = async (queryParams) => { + try { + const response = await ApiRequest(queryParams); + console.log('📦 SendRequest response:', { + hasError: response.error, + status: response.status, + statusCode: response.data?.statusCode, + data: response.data, + }); + + // If ApiRequest returned error flag, return error structure + if (response.error) { + const errorMsg = response.data?.message || response.statusText || 'Request failed'; + console.error('❌ SendRequest error response:', errorMsg); + + // Return consistent error structure instead of empty array + return { + statusCode: response.status || 500, + message: errorMsg, + data: null, + error: true, + }; + } + + return response || { statusCode: 200, data: [], message: 'Success' }; + } catch (error) { + console.error('❌ SendRequest catch error:', error); + + // Don't show Swal here, let the calling code handle it + // This allows better error handling in each API call + return { + statusCode: 500, + message: error.message || 'Something went wrong', + data: null, + error: true, + }; + } +}; + +export { ApiRequest, SendRequest }; diff --git a/src/pages/Example.jsx b/src/pages/Example.jsx new file mode 100644 index 0000000..1e0b2ab --- /dev/null +++ b/src/pages/Example.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import axios from 'axios'; + +const Example = () => { + + // State to store the fetched data + const [posts, setPosts] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(null); + + // Function to fetch data from API + const fetchPosts = async () => { + try { + setLoading(true); + setError(null); + // Make GET request to JSONPlaceholder API + const response = await axios.get('https://jsonplaceholder.typicode.com/posts'); + setPosts(response.data); + } catch (err) { + setError(err.message); + console.error('Error fetching data:', err); + } finally { + setLoading(false); + } + }; + + // Use useEffect to fetch data when component mounts + React.useEffect(() => { + fetchPosts(); + }, []); // Empty dependency array means this runs once on mount + + // Function to fetch a single post (example of another API call) + const fetchSinglePost = async (postId) => { + try { + const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}`); + console.log('Single post:', response.data); + // Handle the single post data as needed + } catch (err) { + console.error('Error fetching single post:', err); + } + }; + + // Function to create a new post (example of POST request) + const createPost = async () => { + try { + const newPost = { + title: 'New Post', + body: 'This is a new post created via API', + userId: 1, + }; + const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost); + console.log('Created post:', response.data); + // Optionally refetch posts after creation + fetchPosts(); + } catch (err) { + console.error('Error creating post:', err); + } + }; + + if (loading) { + return
Loading posts...
; + } + + if (error) { + return
Error: {error}
; + } + + // return(
Example
) + + return ( +
+

Example Posts from JSONPlaceholder

+ + + + + +
+ {posts.slice(0, 5).map((post) => ( +
fetchSinglePost(post.id)} + > +

{post.title}

+

{post.body}

+ Post ID: {post.id} | User ID: {post.userId} +
+ ))} +
+ +

Showing {Math.min(posts.length, 5)} of {posts.length} posts

+
+ ); + +} + +export default Example; \ No newline at end of file diff --git a/src/pages/TheChild.jsx b/src/pages/TheChild.jsx new file mode 100644 index 0000000..791b356 --- /dev/null +++ b/src/pages/TheChild.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const TheChild = () => { + return(
Hahalo
) +} + +export default TheChild; \ No newline at end of file