-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b2cfeaa
commit 0eb88b2
Showing
9 changed files
with
639 additions
and
244 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { ApiCall } from "@/reducers/app"; | ||
import dynamic from "next/dynamic"; | ||
import { useState, useEffect } from "react"; | ||
import { Store } from "redux"; | ||
|
||
const CodeComponent = dynamic(() => import('@/components/CodeEditor').then((e) => e.CodeComponent), { | ||
ssr: false, | ||
}); | ||
|
||
export const ApiCallComponent: React.FC<{ | ||
content: ApiCall, | ||
store: Store, | ||
onResponse: (response: any) => void | ||
}> = (props) => { | ||
const [response, setResponse] = useState<any>(null); | ||
const [isPopupOpen, setIsPopupOpen] = useState(false); | ||
const [isCurlPopupOpen, setIsCurlPopupOpen] = useState(false); | ||
const [requestStatus, setRequestStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); | ||
|
||
const { content } = props; | ||
|
||
const url = content.endpoint(props.store); | ||
const body = content.requestBody(props.store); | ||
|
||
const curlCommand = content.curlCommand( | ||
url, | ||
content.method, | ||
body | ||
); | ||
|
||
useEffect(() => { | ||
const handleKeyDown = (e) => { | ||
if (e.key === "Escape") { | ||
setIsPopupOpen(false); | ||
setIsCurlPopupOpen(false); | ||
} | ||
}; | ||
|
||
if (isPopupOpen || isCurlPopupOpen) { | ||
document.addEventListener("keydown", handleKeyDown); | ||
} else { | ||
document.removeEventListener("keydown", handleKeyDown); | ||
} | ||
|
||
return () => { | ||
document.removeEventListener("keydown", handleKeyDown); | ||
}; | ||
}, [isPopupOpen, isCurlPopupOpen]); | ||
|
||
const handleRunInBrowser = () => { | ||
setRequestStatus('loading'); | ||
fetch(url, { | ||
method: content.method, | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: content.method === "POST" ? JSON.stringify(body) : undefined, | ||
}) | ||
.then((response) => { | ||
if (!response.ok) { | ||
throw new Error(`HTTP error! status: ${response.status}`); | ||
} | ||
return response.json(); | ||
}) | ||
.then((data) => { | ||
console.log("API response:", data); | ||
setResponse(data); | ||
if (props.onResponse) { | ||
props.onResponse(data) | ||
} | ||
setRequestStatus('success'); | ||
}) | ||
.catch((error) => { | ||
console.error("API call error:", error); | ||
setRequestStatus('error'); | ||
}); | ||
}; | ||
|
||
const handleCopyCurl = () => { | ||
setIsCurlPopupOpen(true); | ||
}; | ||
|
||
let runButtonClasses = | ||
"px-3 py-1 text-sm text-white rounded-lg transition duration-200 "; | ||
|
||
switch (requestStatus) { | ||
case 'idle': | ||
runButtonClasses += "bg-gray-500 hover:bg-gray-400"; | ||
break; | ||
case 'loading': | ||
runButtonClasses += "bg-blue-500 hover:bg-blue-400"; | ||
break; | ||
case 'success': | ||
runButtonClasses += "bg-green-500 hover:bg-green-400"; | ||
break; | ||
case 'error': | ||
runButtonClasses += "bg-red-500 hover:bg-red-400"; | ||
break; | ||
default: | ||
runButtonClasses += "bg-gray-500 hover:bg-gray-400"; | ||
} | ||
|
||
return ( | ||
<div className="my-4"> | ||
<div className="flex items-center mb-2"> | ||
<div className="flex-grow"> | ||
<p className="text-lg font-semibold text-gray-800"> | ||
{content.title} | ||
</p> | ||
<p className="text-xs font-semibold text-gray-800"> | ||
{content.description} | ||
</p> | ||
</div> | ||
</div> | ||
<div className="flex space-x-2"> | ||
<button | ||
className={runButtonClasses} | ||
onClick={handleRunInBrowser} | ||
> | ||
Run in Browser | ||
</button> | ||
<button | ||
className="px-3 py-1 text-sm bg-blue-500 text-white rounded-lg hover:bg-blue-400 transition duration-200" | ||
onClick={handleCopyCurl} | ||
> | ||
Show me cURL | ||
</button> | ||
{response && ( | ||
<button | ||
className="px-3 py-1 text-sm bg-gray-500 text-white rounded-lg hover:bg-gray-400 transition duration-200" | ||
onClick={() => setIsPopupOpen(true)} | ||
> | ||
Show me response | ||
</button> | ||
)} | ||
</div> | ||
|
||
{/* Response Modal */} | ||
{isPopupOpen && ( | ||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"> | ||
<div className="bg-white p-6 rounded-lg max-w-2xl mx-auto overflow-y-auto max-h-screen shadow-lg"> | ||
<h2 className="text-lg font-semibold mb-4 text-blue-700">API Response</h2> | ||
<pre className="bg-gray-800 text-white p-4 rounded-lg shadow-md overflow-auto max-h-80 whitespace-pre-wrap"> | ||
<code>{JSON.stringify(response, null, 2)}</code> | ||
</pre> | ||
<div className="mt-4 flex justify-end"> | ||
<button | ||
className="mt-4 px-3 py-1 text-sm bg-red-500 text-white rounded-lg hover:bg-red-400 transition duration-200" | ||
onClick={() => setIsPopupOpen(false)} | ||
> | ||
Close | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
|
||
{/* cURL Modal */} | ||
{isCurlPopupOpen && ( | ||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"> | ||
<div className="bg-white p-6 rounded-lg max-w-2xl mx-auto overflow-y-auto max-h-screen shadow-lg"> | ||
<h2 className="text-lg font-semibold mb-4 text-blue-700">cURL Command</h2> | ||
<CodeComponent content={{ language: 'bash', code: curlCommand }} /> | ||
|
||
<div className="mt-4 flex justify-end"> | ||
<button | ||
className="px-3 py-1 text-sm bg-red-500 text-white rounded-lg hover:bg-red-400 transition duration-200" | ||
onClick={() => setIsCurlPopupOpen(false)} | ||
> | ||
Close | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use client'; | ||
|
||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; | ||
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; | ||
import { CheckIcon, ClipboardCopyIcon } from '@heroicons/react/outline'; | ||
import { useState } from 'react'; | ||
import { CodeBlock } from '@/reducers/app'; | ||
|
||
|
||
export const CodeComponent: React.FC<{ content: CodeBlock }> = ({ content }) => { | ||
const [copied, setCopied] = useState(false); | ||
|
||
const copyToClipboard = (text: string) => { | ||
navigator.clipboard.writeText(text).then(() => { | ||
setCopied(true); | ||
setTimeout(() => setCopied(false), 2000); | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="relative mb-6"> | ||
<SyntaxHighlighter | ||
language={content.language || 'javascript'} | ||
style={oneDark} | ||
showLineNumbers | ||
customStyle={{ | ||
borderRadius: '0.5rem', | ||
padding: '1.5rem', | ||
backgroundColor: '#282c34', | ||
fontSize: '0.875rem', | ||
overflowX: 'auto', | ||
}} | ||
lineNumberStyle={{ color: '#6c757d' }} | ||
> | ||
{content.code} + xd | ||
</SyntaxHighlighter> | ||
<button | ||
onClick={() => copyToClipboard(content.code)} | ||
className="absolute top-4 right-4 bg-blue-600 hover:bg-blue-500 text-white font-medium py-1 px-2 rounded transition duration-200 flex items-center" | ||
> | ||
{copied ? ( | ||
<> | ||
<CheckIcon className="w-4 h-4 mr-1" /> Copied | ||
</> | ||
) : ( | ||
<> | ||
<ClipboardCopyIcon className="w-4 h-4 mr-1" /> Copy | ||
</> | ||
)} | ||
</button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Component } from "@/reducers/app"; | ||
|
||
export const InteractiveComponent: React.FC<{ content: Component }> = props => { | ||
const { content } = props; | ||
return content({}) | ||
} |
Oops, something went wrong.