add a page

This commit is contained in:
yogiedigital
2025-12-04 20:21:32 +07:00
parent dd1e56c5b8
commit 84cedc1855
10 changed files with 578 additions and 17 deletions

View File

@@ -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>

View File

View File

View 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
View 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
View File

@@ -0,0 +1,7 @@
import React from 'react';
const TheChild = () => {
return(<div>Hahalo</div>)
}
export default TheChild;