@@ -11,7 +11,7 @@ import { CopyButton, StatusBadge, TimestampTooltip } from "@dub/ui";
1111import { ChevronRight , StackY3 } from "@dub/ui/icons" ;
1212import { fetcher , formatDateTime } from "@dub/utils" ;
1313import Link from "next/link" ;
14- import { useParams } from "next/navigation" ;
14+ import { redirect , useParams } from "next/navigation" ;
1515import { useEffect , useState } from "react" ;
1616import type { HighlighterCore } from "shiki" ;
1717import useSWR from "swr" ;
@@ -29,6 +29,10 @@ export function LogDetailPageClient() {
2929 fetcher ,
3030 ) ;
3131
32+ if ( error ?. status === 404 ) {
33+ redirect ( `/${ slug } /settings/logs` ) ;
34+ }
35+
3236 return (
3337 < PageContent
3438 title = {
@@ -72,10 +76,28 @@ export function LogDetailPageClient() {
7276 ) ;
7377}
7478
79+ function LogDetailSkeleton ( ) {
80+ return (
81+ < div className = "flex flex-col gap-6 lg:flex-row" >
82+ < div className = "order-last min-w-0 flex-1 lg:order-first" >
83+ < div className = "flex flex-col gap-6" >
84+ < div className = "h-48 animate-pulse rounded-xl bg-neutral-200" />
85+ < div className = "h-48 animate-pulse rounded-xl bg-neutral-200" />
86+ </ div >
87+ </ div >
88+ < div className = "order-first w-full shrink-0 lg:order-last lg:w-[360px]" >
89+ < div className = "h-64 animate-pulse rounded-xl bg-neutral-200" />
90+ </ div >
91+ </ div >
92+ ) ;
93+ }
94+
7595function LogDetailContent ( { log } : { log : EnrichedApiLog } ) {
7696 const [ highlighter , setHighlighter ] = useState < HighlighterCore | null > ( null ) ;
77- const [ highlightedRequest , setHighlightedRequest ] = useState ( "" ) ;
78- const [ highlightedResponse , setHighlightedResponse ] = useState ( "" ) ;
97+ const [ highlightedBodies , setHighlightedBodies ] = useState < {
98+ request : string ;
99+ response : string ;
100+ } | null > ( null ) ;
79101
80102 useEffect ( ( ) => {
81103 import ( "shiki" ) . then ( ( { createHighlighter } ) => {
@@ -87,27 +109,37 @@ function LogDetailContent({ log }: { log: EnrichedApiLog }) {
87109 } , [ ] ) ;
88110
89111 useEffect ( ( ) => {
90- if ( ! highlighter ) return ;
112+ if ( ! highlighter ) {
113+ setHighlightedBodies ( null ) ;
114+ return ;
115+ }
91116
92- const toHighlightedJson = ( raw : string ) => {
117+ const toJsonString = ( raw : string ) => {
93118 let value : unknown ;
94119 try {
95120 value = JSON . parse ( raw ) ;
96121 } catch {
97122 value = raw ;
98123 }
124+ return JSON . stringify ( value , null , 2 ) ?? String ( value ) ;
125+ } ;
99126
100- const jsonStr = JSON . stringify ( value , null , 2 ) ?? String ( value ) ;
101- return highlighter . codeToHtml ( jsonStr , {
127+ setHighlightedBodies ( {
128+ request : highlighter . codeToHtml ( toJsonString ( log . request_body ) , {
102129 theme : "min-light" ,
103130 lang : "json" ,
104- } ) ;
105- } ;
106-
107- setHighlightedRequest ( toHighlightedJson ( log . request_body ) ) ;
108- setHighlightedResponse ( toHighlightedJson ( log . response_body ) ) ;
131+ } ) ,
132+ response : highlighter . codeToHtml ( toJsonString ( log . response_body ) , {
133+ theme : "min-light" ,
134+ lang : "json" ,
135+ } ) ,
136+ } ) ;
109137 } , [ highlighter , log ] ) ;
110138
139+ if ( ! highlightedBodies ) {
140+ return < LogDetailSkeleton /> ;
141+ }
142+
111143 const detailRows : Record < string , React . ReactNode > = {
112144 Path : (
113145 < span className = "truncate font-mono" title = { log . path } >
@@ -184,10 +216,10 @@ function LogDetailContent({ log }: { log: EnrichedApiLog }) {
184216 < h3 className = "text-content-emphasis text-lg font-semibold" >
185217 Response body
186218 </ h3 >
187- { highlightedResponse ? (
219+ { highlightedBodies . response ? (
188220 < div
189221 className = "shiki-wrapper max-h-[800px] overflow-auto rounded-xl border border-neutral-200 bg-white p-4 text-sm"
190- dangerouslySetInnerHTML = { { __html : highlightedResponse } }
222+ dangerouslySetInnerHTML = { { __html : highlightedBodies . response } }
191223 />
192224 ) : (
193225 < div className = "rounded-xl border border-neutral-200 bg-white p-4 font-mono text-xs text-neutral-500" >
@@ -200,10 +232,12 @@ function LogDetailContent({ log }: { log: EnrichedApiLog }) {
200232 < h3 className = "text-content-emphasis text-lg font-semibold" >
201233 Request body
202234 </ h3 >
203- { highlightedRequest ? (
235+ { highlightedBodies . request ? (
204236 < div
205237 className = "shiki-wrapper max-h-[800px] overflow-auto rounded-xl border border-neutral-200 bg-white p-4 text-sm"
206- dangerouslySetInnerHTML = { { __html : highlightedRequest } }
238+ dangerouslySetInnerHTML = { {
239+ __html : highlightedBodies . request ,
240+ } }
207241 />
208242 ) : (
209243 < div className = "rounded-xl border border-neutral-200 bg-white p-4 font-mono text-xs text-neutral-500" >
@@ -241,19 +275,3 @@ function LogDetailContent({ log }: { log: EnrichedApiLog }) {
241275 </ div >
242276 ) ;
243277}
244-
245- function LogDetailSkeleton ( ) {
246- return (
247- < div className = "flex flex-col gap-6 lg:flex-row" >
248- < div className = "order-last min-w-0 flex-1 lg:order-first" >
249- < div className = "flex flex-col gap-6" >
250- < div className = "h-48 animate-pulse rounded-xl bg-neutral-200" />
251- < div className = "h-48 animate-pulse rounded-xl bg-neutral-200" />
252- </ div >
253- </ div >
254- < div className = "order-first w-full shrink-0 lg:order-last lg:w-[360px]" >
255- < div className = "h-64 animate-pulse rounded-xl bg-neutral-200" />
256- </ div >
257- </ div >
258- ) ;
259- }
0 commit comments