Skip to content

Commit 1dcddee

Browse files
Merge branch 'main' of https://github.com/reactplay/react-play into fix/navbar-scroll-behavior
2 parents e29611b + b905e13 commit 1dcddee

18 files changed

Lines changed: 3017 additions & 32 deletions

File tree

.env.development renamed to .env.example

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ REACT_APP_NHOST_VERSION=v1
77
REACT_APP_NHOST_ENDPOINT=graphql
88
REACT_APP_PLAY_WEB_SVC=https://api.reactplay.io/.netlify/functions/server
99
DISABLE_ESLINT_PLUGIN=true
10-
REACT_APP_ACTIVITIES_ON=false
10+
REACT_APP_ACTIVITIES_ON=true
1111
REACT_APP_ACTIVITY_ID=hackrplay
1212
REACT_APP_DADJOKES_URL=https://jokeapi-v2.p.rapidapi.com/joke/
13-
REACT_APP_DADJOKES_APIKEY='b71df95c75msha446fab91d0e935p1d0262jsn1d938cb85502'
14-
REACT_APP_DADJOKES_APIHOST='jokeapi-v2.p.rapidapi.com'
13+
REACT_APP_DADJOKES_APIKEY=your_rapidapi_key_here
14+
REACT_APP_DADJOKES_APIHOST='jokeapi-v2.p.rapidapi.com'
15+
16+
# Add your API keys below:
17+
# REACT_APP_SEARCH_APIKEY=your_search_api_key
18+
# REACT_APP_TUBETUNES_APIKEY=your_tubetunes_api_key
19+
# REACT_APP_WEATHER_API_KEY=your_weather_api_key
20+
# REACT_APP_FIREBASE_API_KEY=your_firebase_api_key
21+
# REACT_APP_DIGITSDELIGHT_APIKEY=your_digitsdelight_api_key

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
# misc
1717
.env
18+
.env.development
1819
.DS_Store
1920
.env
2021
.env.local

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Once forked, you can clone the repo by clicking the `Clone or Download` button o
8888

8989
Please change the directory after cloning the repository using the `cd <folder-name>` command.
9090

91-
> **Note:** Please do not remove the `.env.development` file from the root folder. It contains all the environment variables required for development.
91+
> **Note:** Copy `.env.example` to `.env.development` and add your API keys. The `.env.development` file is git-ignored to keep secrets secure.
9292
9393
### ⬇️ Install Dependencies
9494

‎netlify.toml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
[build]
2+
command = "npm run build"
3+
publish = "build"
4+
5+
[[headers]]
6+
for = "/*"
7+
[headers.values]
8+
X-Frame-Options = "DENY"
9+
110
[[redirects]]
211
from = "/*"
312
to = "/index.html"

‎src/common/header/HeaderNav.jsx‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@ const HeaderNav = ({ showBrowse }) => {
121121
>
122122
<span className="navbar-toggler-icon" />
123123
</button>
124-
<div className={showToggleMenu ? 'navbar-collapse show' : 'navbar-collapse'}>
125-
<ul className="header-links" data-testid="header-links-container">
124+
<div
125+
className={showToggleMenu ? 'navbar-collapse show' : 'navbar-collapse'}
126+
role="presentation"
127+
onClick={() => setShowToggleMenu(false)}
128+
>
129+
<ul
130+
className="header-links"
131+
data-testid="header-links-container"
132+
onClick={(e) => e.stopPropagation()}
133+
>
126134
<li className="menu-closer">
127135
<button
128136
className="navbar-closer"

‎src/common/header/header.css‎

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,51 @@
111111
display: none;
112112
}
113113

114+
@media screen and (max-width: 1217px) {
115+
.navbar-collapse {
116+
display: block !important;
117+
position: fixed;
118+
top: 0;
119+
right: 0;
120+
bottom: 0;
121+
left: 0;
122+
visibility: hidden;
123+
pointer-events: none;
124+
}
125+
126+
.navbar-collapse:before {
127+
content: '';
128+
background-color: var(--color-neutral-90);
129+
position: fixed;
130+
top: 0;
131+
right: 0;
132+
bottom: 0;
133+
left: 0;
134+
z-index: 0;
135+
opacity: 0;
136+
cursor: pointer;
137+
transition: opacity 0.3s ease;
138+
}
139+
140+
.navbar-collapse .header-links {
141+
transform: translateX(100%);
142+
transition: transform 0.3s ease;
143+
}
144+
145+
.navbar-collapse.show {
146+
visibility: visible;
147+
pointer-events: auto;
148+
}
149+
150+
.navbar-collapse.show:before {
151+
opacity: 60%;
152+
}
153+
154+
.navbar-collapse.show .header-links {
155+
transform: translateX(0);
156+
}
157+
}
158+
114159
@media (min-width: 1217px) {
115160
.navbar-toggler {
116161
display: none;
@@ -186,28 +231,7 @@
186231
}
187232

188233
@media screen and (max-width: 1217px) {
189-
.navbar-collapse.show {
190-
display: block;
191-
position: fixed;
192-
top: 0;
193-
right: 0;
194-
bottom: 0;
195-
left: 0;
196-
}
197-
198-
.navbar-collapse.show:before {
199-
content: '';
200-
background-color: var(--color-neutral-90);
201-
position: fixed;
202-
top: 0;
203-
right: 0;
204-
bottom: 0;
205-
left: 0;
206-
z-index: 0;
207-
opacity: 60%;
208-
}
209-
210-
.navbar-collapse.show .header-links {
234+
.navbar-collapse .header-links {
211235
position: absolute;
212236
top: 0;
213237
right: 0;
@@ -224,16 +248,13 @@
224248

225249
.navbar-collapse.show .header-links {
226250
align-items: stretch;
251+
grid-gap: 0.2rem;
227252
}
228253

229254
.navbar-collapse .menu-closer {
230255
display: block;
231256
}
232257

233-
.navbar-collapse.show .header-links {
234-
grid-gap: 0.2rem;
235-
}
236-
237258
.header-links > li.menu-spacer {
238259
margin: 0.4rem 0 1rem 0;
239260
}
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;

0 commit comments

Comments
 (0)