summaryrefslogtreecommitdiffstats
path: root/src/components/Details
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2021-12-30 09:51:00 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2022-01-11 00:00:22 +0100
commitbcb77d979d817e1e609adb4d007bbbcc3f61efbd (patch)
tree093c5a2914ee0f6e1ec559b3b98725190fee7285 /src/components/Details
downloadclient-1.0.0.tar.gz
client-1.0.0.tar.bz2
client-1.0.0.zip
Prepare for githubv1.0.0
Diffstat (limited to 'src/components/Details')
-rw-r--r--src/components/Details/Details.module.scss84
-rw-r--r--src/components/Details/Details.tsx148
2 files changed, 232 insertions, 0 deletions
diff --git a/src/components/Details/Details.module.scss b/src/components/Details/Details.module.scss
new file mode 100644
index 0000000..32197f0
--- /dev/null
+++ b/src/components/Details/Details.module.scss
@@ -0,0 +1,84 @@
1.details {
2 height: 100%;
3 display: flex;
4 flex-flow: column nowrap;
5 align-items: stretch;
6 overflow: hidden;
7}
8
9.header {
10 flex-shrink: 0;
11 flex-grow: 0;
12}
13
14.content {
15 flex-grow: 1;
16 flex-shrink: 1;
17 overflow-y: auto;
18}
19
20.summary {
21 font-size: 2em;
22}
23
24.tabs {
25 display: flex;
26 border-bottom: 1px solid black;
27
28 &:last-child {
29 flex-grow: 1;
30 }
31}
32
33.tab {
34 background-color: white;
35 border: none;
36 border-top: 1px solid black;
37 border-right: 1px solid black;
38 border-radius: 0;
39 color: black;
40 font-size: 1.2em;
41 padding: 0.5em;
42
43 &.selected {
44 background-color: black;
45 color: white;
46 }
47}
48
49.emptySpace {
50 flex-grow: 1;
51 display: flex;
52 justify-content: flex-end;
53 align-items: center;
54 font-size: 1.2em;
55 padding: 0.5em;
56}
57
58.resend {
59 margin-left: 1em;
60}
61
62.headers {
63 display: grid;
64 grid-template-columns: auto 1fr;
65 border: 1px solid black;
66 margin: 1em;
67 overflow-x: auto;
68
69 h2 {
70 grid-column: 1/ span 2;
71 background-color: black;
72 color: white;
73 padding: 0.5em;
74 }
75
76 > div {
77 line-break: auto;
78 padding: 0.5em;
79
80 &:nth-of-type(4n), &:nth-of-type(4n - 1) {
81 background-color: lightgray;
82 }
83 }
84}
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}