Skip to content

Commit ce3479f

Browse files
committed
Added api tester
1 parent 324b3fe commit ce3479f

File tree

6 files changed

+1588
-0
lines changed

6 files changed

+1588
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import PlayHeader from 'common/playlists/PlayHeader';
2+
import { useState, useEffect } from 'react';
3+
import RequestPanel from './components/RequestPanel';
4+
import ResponsePanel from './components/ResponsePanel';
5+
import HistoryPanel from './components/HistoryPanel';
6+
import './styles.css';
7+
8+
function ApiRequestBuilder(props) {
9+
const [method, setMethod] = useState('GET');
10+
const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/posts/1');
11+
const [headers, setHeaders] = useState([
12+
{ key: 'Content-Type', value: 'application/json', enabled: true }
13+
]);
14+
const [body, setBody] = useState('');
15+
const [response, setResponse] = useState(null);
16+
const [isLoading, setIsLoading] = useState(false);
17+
const [history, setHistory] = useState([]);
18+
const [activeTab, setActiveTab] = useState('body');
19+
const [bodyType, setBodyType] = useState('json');
20+
const [showHistory, setShowHistory] = useState(false);
21+
22+
// Load history from localStorage on mount
23+
useEffect(() => {
24+
const savedHistory = localStorage.getItem('api-request-history');
25+
if (savedHistory) {
26+
try {
27+
setHistory(JSON.parse(savedHistory));
28+
} catch (error) {
29+
console.error('Failed to load history:', error);
30+
}
31+
}
32+
}, []);
33+
34+
// Save history to localStorage
35+
const saveToHistory = (request, response) => {
36+
const historyItem = {
37+
id: Date.now(),
38+
timestamp: new Date().toISOString(),
39+
method: request.method,
40+
url: request.url,
41+
headers: request.headers,
42+
body: request.body,
43+
response: {
44+
status: response.status,
45+
statusText: response.statusText,
46+
data: response.data,
47+
time: response.time,
48+
size: response.size
49+
}
50+
};
51+
52+
const updatedHistory = [historyItem, ...history].slice(0, 50); // Keep last 50 requests
53+
setHistory(updatedHistory);
54+
localStorage.setItem('api-request-history', JSON.stringify(updatedHistory));
55+
};
56+
57+
const handleSendRequest = async () => {
58+
if (!url.trim()) {
59+
setResponse({
60+
error: true,
61+
message: 'Please enter a valid URL',
62+
status: 0
63+
});
64+
65+
return;
66+
}
67+
68+
setIsLoading(true);
69+
const startTime = Date.now();
70+
71+
try {
72+
// Prepare headers
73+
const requestHeaders = {};
74+
headers.forEach((header) => {
75+
if (header.enabled && header.key.trim()) {
76+
requestHeaders[header.key] = header.value;
77+
}
78+
});
79+
80+
// Prepare request options
81+
const options = {
82+
method: method,
83+
headers: requestHeaders
84+
};
85+
86+
// Add body for methods that support it
87+
if (['POST', 'PUT', 'PATCH'].includes(method) && body.trim()) {
88+
if (bodyType === 'json') {
89+
try {
90+
JSON.parse(body); // Validate JSON
91+
options.body = body;
92+
} catch (e) {
93+
throw new Error('Invalid JSON in request body');
94+
}
95+
} else {
96+
options.body = body;
97+
}
98+
}
99+
100+
const response = await fetch(url, options);
101+
const endTime = Date.now();
102+
const responseTime = endTime - startTime;
103+
104+
let responseData;
105+
const contentType = response.headers.get('content-type');
106+
107+
if (contentType && contentType.includes('application/json')) {
108+
responseData = await response.json();
109+
} else {
110+
responseData = await response.text();
111+
}
112+
113+
const responseSize = new Blob([JSON.stringify(responseData)]).size;
114+
115+
const responseObj = {
116+
status: response.status,
117+
statusText: response.statusText,
118+
data: responseData,
119+
headers: Object.fromEntries(response.headers.entries()),
120+
time: responseTime,
121+
size: responseSize,
122+
error: false
123+
};
124+
125+
setResponse(responseObj);
126+
127+
// Save to history
128+
saveToHistory({ method, url, headers, body }, responseObj);
129+
} catch (error) {
130+
setResponse({
131+
error: true,
132+
message: error.message,
133+
status: 0,
134+
time: Date.now() - startTime
135+
});
136+
} finally {
137+
setIsLoading(false);
138+
}
139+
};
140+
141+
const loadFromHistory = (item) => {
142+
setMethod(item.method);
143+
setUrl(item.url);
144+
setHeaders(item.headers);
145+
setBody(item.body || '');
146+
setResponse(item.response);
147+
setShowHistory(false);
148+
};
149+
150+
const clearHistory = () => {
151+
setHistory([]);
152+
localStorage.removeItem('api-request-history');
153+
};
154+
155+
const formatJSON = () => {
156+
try {
157+
const parsed = JSON.parse(body);
158+
setBody(JSON.stringify(parsed, null, 2));
159+
} catch (error) {
160+
alert('Invalid JSON format');
161+
}
162+
};
163+
164+
return (
165+
<div className="play-details">
166+
<PlayHeader play={props} />
167+
<div className="play-details-body">
168+
<div className="api-builder-container">
169+
<div className="api-builder-header">
170+
<h2 className="api-builder-title">🚀 API Request Builder & Tester</h2>
171+
<button
172+
className="history-toggle-btn"
173+
title="View Request History"
174+
onClick={() => setShowHistory(!showHistory)}
175+
>
176+
📋 History ({history.length})
177+
</button>
178+
</div>
179+
180+
<div className={`api-builder-layout ${showHistory ? 'show-history' : ''}`}>
181+
<div className="api-builder-main">
182+
<RequestPanel
183+
activeTab={activeTab}
184+
body={body}
185+
bodyType={bodyType}
186+
formatJSON={formatJSON}
187+
headers={headers}
188+
isLoading={isLoading}
189+
method={method}
190+
setActiveTab={setActiveTab}
191+
setBody={setBody}
192+
setBodyType={setBodyType}
193+
setHeaders={setHeaders}
194+
setMethod={setMethod}
195+
setUrl={setUrl}
196+
url={url}
197+
onSend={handleSendRequest}
198+
/>
199+
200+
<ResponsePanel isLoading={isLoading} response={response} />
201+
</div>
202+
203+
{showHistory && (
204+
<HistoryPanel
205+
history={history}
206+
onClearHistory={clearHistory}
207+
onClose={() => setShowHistory(false)}
208+
onLoadRequest={loadFromHistory}
209+
/>
210+
)}
211+
</div>
212+
</div>
213+
</div>
214+
</div>
215+
);
216+
}
217+
218+
export default ApiRequestBuilder;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
function HistoryPanel({ history, onLoadRequest, onClearHistory, onClose }) {
2+
const formatDate = (isoString) => {
3+
const date = new Date(isoString);
4+
const now = new Date();
5+
const diffMs = now - date;
6+
const diffMins = Math.floor(diffMs / 60000);
7+
const diffHours = Math.floor(diffMs / 3600000);
8+
const diffDays = Math.floor(diffMs / 86400000);
9+
10+
if (diffMins < 1) return 'Just now';
11+
if (diffMins < 60) return `${diffMins}m ago`;
12+
if (diffHours < 24) return `${diffHours}h ago`;
13+
if (diffDays < 7) return `${diffDays}d ago`;
14+
15+
return date.toLocaleDateString();
16+
};
17+
18+
const getStatusEmoji = (status) => {
19+
if (status >= 200 && status < 300) return '✅';
20+
if (status >= 300 && status < 400) return '↩️';
21+
if (status >= 400 && status < 500) return '⚠️';
22+
23+
return '❌';
24+
};
25+
26+
const getMethodColor = (method) => {
27+
const colors = {
28+
GET: '#28a745',
29+
POST: '#ffc107',
30+
PUT: '#17a2b8',
31+
PATCH: '#6f42c1',
32+
DELETE: '#dc3545',
33+
HEAD: '#6c757d',
34+
OPTIONS: '#343a40'
35+
};
36+
37+
return colors[method] || '#6c757d';
38+
};
39+
40+
return (
41+
<div className="history-panel">
42+
<div className="history-header">
43+
<h3>📋 Request History</h3>
44+
<div className="history-actions">
45+
{history.length > 0 && (
46+
<button
47+
className="clear-history-btn"
48+
title="Clear all history"
49+
onClick={onClearHistory}
50+
>
51+
🗑️ Clear
52+
</button>
53+
)}
54+
<button className="close-history-btn" title="Close history" onClick={onClose}>
55+
56+
</button>
57+
</div>
58+
</div>
59+
60+
<div className="history-content">
61+
{history.length === 0 ? (
62+
<div className="history-empty">
63+
<div className="empty-icon">📭</div>
64+
<p>No requests yet</p>
65+
<small>Your request history will appear here</small>
66+
</div>
67+
) : (
68+
<div className="history-list">
69+
{history.map((item) => (
70+
<div
71+
className="history-item"
72+
key={item.id}
73+
role="button"
74+
tabIndex={0}
75+
onClick={() => onLoadRequest(item)}
76+
onKeyDown={(e) => {
77+
if (e.key === 'Enter' || e.key === ' ') {
78+
onLoadRequest(item);
79+
}
80+
}}
81+
>
82+
<div className="history-item-header">
83+
<span
84+
className="history-method"
85+
style={{ backgroundColor: getMethodColor(item.method) }}
86+
>
87+
{item.method}
88+
</span>
89+
<span className="history-status">
90+
{getStatusEmoji(item.response.status)} {item.response.status}
91+
</span>
92+
<span className="history-time">{formatDate(item.timestamp)}</span>
93+
</div>
94+
<div className="history-item-url" title={item.url}>
95+
{item.url}
96+
</div>
97+
<div className="history-item-footer">
98+
<span>{item.response.time}ms</span>
99+
{item.response.size && <span>📦 {Math.round(item.response.size / 1024)}KB</span>}
100+
</div>
101+
</div>
102+
))}
103+
</div>
104+
)}
105+
</div>
106+
107+
<div className="history-footer">
108+
<small>💡 Click on any request to load it</small>
109+
</div>
110+
</div>
111+
);
112+
}
113+
114+
export default HistoryPanel;

0 commit comments

Comments
 (0)