add a page
This commit is contained in:
50
src/App.jsx
50
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(<div>Hahalo</div>)}
|
||||
// const TheChild = () => {return(<div>Hahalo</div>)}
|
||||
const size = 'small';
|
||||
const App = () => {
|
||||
// const [stateOpenKeys, setStateOpenKeys] = useState(['2', '23']);
|
||||
const [stateOpenKeys, setStateOpenKeys] = useState(['0']);
|
||||
@@ -97,7 +101,7 @@ const App = () => {
|
||||
// children: <TheChild />,
|
||||
// },
|
||||
]);
|
||||
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 = <Example />
|
||||
} 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 (
|
||||
<DefaultTabBar {...props}>
|
||||
{(node) => {
|
||||
// Filter out the add button node
|
||||
if (node.key && node.key.toString().startsWith('rc-tabs-add')) {
|
||||
return null;
|
||||
}
|
||||
return node;
|
||||
}}
|
||||
</DefaultTabBar>
|
||||
);
|
||||
};
|
||||
// const renderTabBar = (props, DefaultTabBar) => {
|
||||
// return (
|
||||
// <DefaultTabBar {...props}>
|
||||
// {(node) => {
|
||||
// // Filter out the add button node
|
||||
// if (node.key && node.key.toString().startsWith('rc-tabs-add')) {
|
||||
// return null;
|
||||
// }
|
||||
// return node;
|
||||
// }}
|
||||
// </DefaultTabBar>
|
||||
// );
|
||||
// };
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
0
src/Utils/Auth/Logout.jsx
Normal file
0
src/Utils/Auth/Logout.jsx
Normal file
0
src/Utils/Auth/Signin.jsx
Normal file
0
src/Utils/Auth/Signin.jsx
Normal file
171
src/components/Global/ApiRequest.jsx
Normal file
171
src/components/Global/ApiRequest.jsx
Normal file
@@ -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 };
|
||||
107
src/pages/Example.jsx
Normal file
107
src/pages/Example.jsx
Normal file
@@ -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 <div>Loading posts...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error}</div>;
|
||||
}
|
||||
|
||||
// return(<div>Example</div>)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Example Posts from JSONPlaceholder</h1>
|
||||
|
||||
<button onClick={fetchPosts} style={{ marginBottom: '20px' }}>
|
||||
Refresh Posts
|
||||
</button>
|
||||
|
||||
<button onClick={createPost} style={{ margin: '0 10px 20px 10px' }}>
|
||||
Create New Post
|
||||
</button>
|
||||
|
||||
<div style={{ display: 'grid', gap: '20px' }}>
|
||||
{posts.slice(0, 5).map((post) => (
|
||||
<div
|
||||
key={post.id}
|
||||
style={{
|
||||
border: '1px solid #ccc',
|
||||
padding: '15px',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => fetchSinglePost(post.id)}
|
||||
>
|
||||
<h3>{post.title}</h3>
|
||||
<p>{post.body}</p>
|
||||
<small>Post ID: {post.id} | User ID: {post.userId}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p>Showing {Math.min(posts.length, 5)} of {posts.length} posts</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default Example;
|
||||
7
src/pages/TheChild.jsx
Normal file
7
src/pages/TheChild.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const TheChild = () => {
|
||||
return(<div>Hahalo</div>)
|
||||
}
|
||||
|
||||
export default TheChild;
|
||||
Reference in New Issue
Block a user