|
| 1 | +import { useState } from 'react'; |
| 2 | + |
| 3 | +interface Header { |
| 4 | + key: string; |
| 5 | + value: string; |
| 6 | +} |
| 7 | + |
| 8 | +export default function RequestBuilder({ onResponse, isLoading, setIsLoading }) { |
| 9 | + const [method, setMethod] = useState('GET'); |
| 10 | + const [url, setUrl] = useState('/'); |
| 11 | + const [headers, setHeaders] = useState<Header[]>([ |
| 12 | + { key: 'Content-Type', value: 'application/json' }, |
| 13 | + ]); |
| 14 | + const [body, setBody] = useState( |
| 15 | + `{\n "title": "foo",\n "body": "bar",\n "userId": 1\n}`, |
| 16 | + ); |
| 17 | + |
| 18 | + const addHeader = () => setHeaders([...headers, { key: '', value: '' }]); |
| 19 | + const removeHeader = (i: number) => setHeaders(headers.filter((_, idx) => idx !== i)); |
| 20 | + |
| 21 | + const updateHeader = (index: number, field: 'key' | 'value', val: string) => { |
| 22 | + const newHeaders = [...headers]; |
| 23 | + newHeaders[index][field] = val; |
| 24 | + setHeaders(newHeaders); |
| 25 | + }; |
| 26 | + |
| 27 | + const sendRequest = async () => { |
| 28 | + setIsLoading(true); |
| 29 | + const start = Date.now(); |
| 30 | + |
| 31 | + try { |
| 32 | + const headerObj = Object.fromEntries( |
| 33 | + headers.filter((h) => h.key && h.value).map((h) => [h.key, h.value]), |
| 34 | + ); |
| 35 | + |
| 36 | + const opts: RequestInit = { |
| 37 | + method, |
| 38 | + headers: headerObj, |
| 39 | + body: method !== 'GET' && method !== 'HEAD' ? body : undefined, |
| 40 | + }; |
| 41 | + |
| 42 | + const res = await fetch(url, opts); |
| 43 | + const end = Date.now(); |
| 44 | + |
| 45 | + const contentType = res.headers.get('content-type'); |
| 46 | + const data = contentType?.includes('json') ? await res.json() : await res.text(); |
| 47 | + |
| 48 | + onResponse({ |
| 49 | + status: res.status, |
| 50 | + statusText: res.statusText, |
| 51 | + headers: Object.fromEntries(res.headers.entries()), |
| 52 | + data, |
| 53 | + time: end - start, |
| 54 | + }); |
| 55 | + } catch (err: any) { |
| 56 | + onResponse({ |
| 57 | + status: 0, |
| 58 | + statusText: 'Error', |
| 59 | + headers: {}, |
| 60 | + data: err.message, |
| 61 | + time: Date.now() - start, |
| 62 | + }); |
| 63 | + } finally { |
| 64 | + setIsLoading(false); |
| 65 | + } |
| 66 | + }; |
| 67 | + |
| 68 | + const config = JSON.parse(document.getElementById('_config').textContent); |
| 69 | + |
| 70 | + return ( |
| 71 | + <div className="card"> |
| 72 | + <div> |
| 73 | + <label> |
| 74 | + URL{' '} |
| 75 | + <span style={{ fontSize: '14px', opacity: 0.7 }}> |
| 76 | + (base : http://localhost:{config.port ?? 3000}) |
| 77 | + </span> |
| 78 | + </label> |
| 79 | + <div style={{ display: 'flex', gap: '10px' }}> |
| 80 | + <select value={method} onChange={(e) => setMethod(e.target.value)}> |
| 81 | + {['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'].map((m) => ( |
| 82 | + <option key={m}>{m}</option> |
| 83 | + ))} |
| 84 | + </select> |
| 85 | + <input className="input" value={url} onChange={(e) => setUrl(e.target.value)} /> |
| 86 | + </div> |
| 87 | + </div> |
| 88 | + |
| 89 | + <h3>Headers</h3> |
| 90 | + |
| 91 | + {headers.map((h, i) => ( |
| 92 | + <div key={i} style={{ display: 'flex', gap: '10px', marginBottom: '8px' }}> |
| 93 | + <input |
| 94 | + className="input" |
| 95 | + placeholder="key" |
| 96 | + value={h.key} |
| 97 | + onChange={(e) => updateHeader(i, 'key', e.target.value)} |
| 98 | + /> |
| 99 | + <input |
| 100 | + className="input" |
| 101 | + placeholder="value" |
| 102 | + value={h.value} |
| 103 | + onChange={(e) => updateHeader(i, 'value', e.target.value)} |
| 104 | + /> |
| 105 | + <button className="button" onClick={() => removeHeader(i)}> |
| 106 | + X |
| 107 | + </button> |
| 108 | + </div> |
| 109 | + ))} |
| 110 | + |
| 111 | + <button className="button" onClick={addHeader}> |
| 112 | + Add Header |
| 113 | + </button> |
| 114 | + |
| 115 | + <h3>Body</h3> |
| 116 | + <textarea |
| 117 | + className="textarea" |
| 118 | + disabled={method === 'GET' || method === 'HEAD'} |
| 119 | + value={body} |
| 120 | + onChange={(e) => setBody(e.target.value)} |
| 121 | + /> |
| 122 | + {(method === 'GET' || method === 'HEAD') && ( |
| 123 | + <p style={{ fontSize: '12px', opacity: 0.7 }}> |
| 124 | + GET/HEAD requests cannot use a body. |
| 125 | + </p> |
| 126 | + )} |
| 127 | + |
| 128 | + <button |
| 129 | + className="button" |
| 130 | + disabled={isLoading} |
| 131 | + onClick={sendRequest} |
| 132 | + style={{ width: '100%', marginTop: '15px' }} |
| 133 | + > |
| 134 | + {isLoading ? 'Sending...' : 'Send Request'} |
| 135 | + </button> |
| 136 | + </div> |
| 137 | + ); |
| 138 | +} |
0 commit comments