summaryrefslogtreecommitdiffstats
path: root/src/components/Details/Details.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/Details/Details.tsx')
-rw-r--r--src/components/Details/Details.tsx148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/components/Details/Details.tsx b/src/components/Details/Details.tsx
new file mode 100644
index 0000000..c4a23f4
--- /dev/null
+++ b/src/components/Details/Details.tsx
@@ -0,0 +1,148 @@
1import * as React from "react";
2import {
3 RequestPayload,
4 RequestResponse,
5 ResponsePayload
6} from "~hooks/useRequests";
7import {useCallback, useEffect, useMemo, useState} from "react";
8import styles from "./Details.module.scss";
9import RequestSummary from "../RequestSummary/RequestSummary";
10import classNames from 'classnames';
11import Content from "../Content/Content";
12import {getHost} from "../../utils";
13
14
15interface TimingProps {
16 timing: number;
17}
18
19
20function Timing({ timing }: TimingProps) {
21 const value = useMemo(() => (Math.round(timing * 1000) / 1000), [timing]);
22 const showSeconds = useMemo(() => value > 1, [value]);
23
24 return !Number.isNaN(value)
25 ? (
26 <>
27 {`${showSeconds ? value : value * 1000}${ showSeconds ? 's' : 'ms' }`}
28 </>
29 )
30 : null;
31}
32
33interface HeadersProps {
34 title: string
35 headers: {
36 [key: string]: string
37 }
38}
39
40function Headers({ title, headers }: HeadersProps) {
41 return (
42 <div className={styles.headers}>
43 <h2>{ title }</h2>
44 {
45 Object.entries(headers).map(([key, value]) => (
46 <>
47 <div>{key}</div>
48 <div>{value}</div>
49 </>
50 ))
51 }
52 </div>
53 )
54}
55
56interface DetailsProps {
57 requestResponse: RequestResponse
58}
59
60type Tab = 'headers' | 'request' | 'response'
61
62export default function Details({ requestResponse }: DetailsProps) {
63 const [tab, selectTab] = useState<Tab>('headers');
64 const [raw, setRaw] = useState(false);
65
66 const resend = useCallback(async () => fetch(
67 `http://${getHost()}/resend/`,
68 {
69 method: 'POST',
70 body: JSON.stringify({
71 ...requestResponse.request,
72 id: undefined
73 })
74 }
75 ), [requestResponse]);
76
77 return (
78 <div className={styles.details}>
79 <div className={styles.header}>
80 <RequestSummary requestResponse={requestResponse} className={styles.summary}/>
81
82 <div className={styles.tabs}>
83 <button
84 onClick={() => selectTab('headers')}
85 className={classNames(styles.tab, {
86 [styles.selected]: tab === 'headers'
87 })}
88 disabled={requestResponse.response === undefined}
89 >
90 Headers
91 </button>
92 <button
93 onClick={() => selectTab('request')}
94 className={classNames(styles.tab, {
95 [styles.selected]: tab === 'request'
96 })}
97 >
98 Request
99 </button>
100 <button
101 onClick={() => selectTab('response')}
102 className={classNames(styles.tab, {
103 [styles.selected]: tab === 'response'
104 })}
105 disabled={requestResponse.response === undefined}
106 >
107 Response
108 </button>
109
110 <div className={styles.emptySpace}>
111 <Timing timing={requestResponse.response?.timing ?? NaN} />
112 <button className={styles.resend} onClick={() => resend()}>Resend</button>
113 </div>
114 </div>
115 </div>
116 <div className={styles.content}>
117 {
118 tab === 'headers' && (
119 <>
120 <Headers
121 title="Request Headers"
122 headers={requestResponse.request.headers}
123 />
124 {
125 requestResponse.response && (
126 <Headers
127 title="Response Headers"
128 headers={requestResponse.response.headers}
129 />
130 )
131 }
132 </>
133 )
134 }
135 {
136 tab === 'request' && (
137 <Content data={requestResponse.request} raw={raw} setRaw={setRaw}/>
138 )
139 }
140 {
141 tab === 'response' && requestResponse.response !== undefined && (
142 <Content data={requestResponse.response} raw={raw} setRaw={setRaw}/>
143 )
144 }
145 </div>
146 </div>
147 )
148}