Skip to content

Commit

Permalink
feat: edit panel state change and panel styling
Browse files Browse the repository at this point in the history
  • Loading branch information
minsgy committed Jun 6, 2024
1 parent dacbcde commit db736a2
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 0 deletions.
125 changes: 125 additions & 0 deletions src/features/RouteEditPanel/CreateEditForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Button } from "@/shared/ui/button"
import { Input } from "@/shared/ui/input"
import { H4, P } from "@/shared/ui/typography"
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/shared/ui/select"
import { format } from "prettier"
import { ResponseJsonEditor } from "./ResponseJsonEditor"
import { StatusSelect } from "./StatusSelect"
import babel from "prettier/plugins/babel"
import prettierPluginEstree from "prettier/plugins/estree"
import { Separator } from "@/shared/ui/separator"
import { Controller, useForm } from "react-hook-form"
import { HttpMethods } from "msw"
import { ROUTE_METHODS } from "@/constants"

type CreateEditFormValues = {
delay?: string
description?: string
method: HttpMethods
url: string
status: string
response: string
}

export const CreateEditForm = () => {
const {
register,
control,
handleSubmit,
setError,
formState: { errors },
} = useForm<CreateEditFormValues>({
defaultValues: {
method: HttpMethods.GET,
url: "",
status: "200",
response: "{}",
},
})

return (
<form
onSubmit={handleSubmit((value) => {
console.log(value)
})}
>
<div className="flex py-[12px] px-[16px]">
<Controller
control={control}
name="method"
render={({ field }) => (
<Select defaultValue={field.value} onValueChange={field.onChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
{ROUTE_METHODS.map((method) => (
<SelectItem key={method} value={method}>
{method}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
<Input
placeholder="Route URL https://example.com/..."
{...register("url")}
/>
<Button type="submit">Save</Button>
</div>
<Separator />
<div className="flex py-[12px] px-[16px]">
<StatusSelect defaultValue={"200"} />
<Input placeholder="Description" {...register("description")} />
<Input placeholder="delay" {...register("delay")} />
</div>
<Separator />
<div className="flex justify-between w-full text-left py-[12px] px-[16px]">
<H4>Response Body</H4>
{errors.response && (
<P className="text-red-500">{errors.response.message}</P>
)}
</div>
<div className="py-[12px] px-[16px] text-left">
<Controller
control={control}
name="response"
render={({ field }) => (
<ResponseJsonEditor
value={field.value}
onChange={(value) => {
console.log(value)
field.onChange(value)
}}
onSave={async () => {
try {
const formattedResponse = await format(field.value, {
tabWidth: 2,
printWidth: 100,
parser: "json5",
plugins: [babel, prettierPluginEstree],
})
// LINK: https://github.com/prettier/prettier/issues/6360
field.onChange(formattedResponse.replace(/[\r\n]+$/, ""))
} catch (error) {
const formattedError =
error instanceof Error ? error.message : "Unknown Error"
setError("response", {
message: formattedError,
})
}
}}
/>
)}
/>
</div>
</form>
)
}
65 changes: 65 additions & 0 deletions src/features/RouteEditPanel/ResponseJsonEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import CodeMirror, {
KeyBinding,
ReactCodeMirrorProps,
hoverTooltip,
keymap,
} from "@uiw/react-codemirror"
import { githubDark } from "@uiw/codemirror-theme-github"
import { linter } from "@codemirror/lint"
import { jsonAutoComplete } from "./utils/jsonAutoComplete"
import { autocompletion, closeBracketsKeymap } from "@codemirror/autocomplete"
import { defaultKeymap, historyKeymap } from "@codemirror/commands"
import { json5, json5ParseLinter } from "codemirror-json5"

export type ResponseJsonEditorProps = ReactCodeMirrorProps & {
onSave?: VoidFunction
}

export const ResponseJsonEditor = ({
onSave,
...rest
}: ResponseJsonEditorProps) => {
const savedKeymap: KeyBinding = {
key: "Ctrl-s",
preventDefault: true,
run: (editor) => {
onSave?.()
return editor.hasFocus
},
}

return (
<CodeMirror
style={{ height: "100%" }}
extensions={[
json5(),
linter(json5ParseLinter(), {
delay: 300,
}),
autocompletion({
defaultKeymap: true,
icons: true,
aboveCursor: true,
activateOnTyping: true,
}),
jsonAutoComplete(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...historyKeymap,
savedKeymap,
]),
]}
theme={githubDark}
basicSetup={{
lineNumbers: true,
highlightActiveLine: true,
highlightActiveLineGutter: true,
indentOnInput: true,
history: true,
bracketMatching: true,
}}
{...rest}
/>
)
}
28 changes: 28 additions & 0 deletions src/features/RouteEditPanel/StatusSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select"
import { STATUS_CODES } from "./constants"
import { SelectProps } from "@radix-ui/react-select"

export const StatusSelect = ({ ...rest }: SelectProps) => {
return (
<Select {...rest}>
<SelectTrigger className="w-[280px]">
<SelectValue placeholder="Select HTTP Status" />
</SelectTrigger>
<SelectContent>
{STATUS_CODES.map(({ label, value }) => {
return (
<SelectItem key={value} value={String(value)}>
{label}
</SelectItem>
)
})}
</SelectContent>
</Select>
)
}
116 changes: 116 additions & 0 deletions src/features/RouteEditPanel/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
export const STATUS_CODES = [
{ value: 100, label: "100 - Continue" },
{ value: 101, label: "101 - Switching Protocols" },
{ value: 102, label: "102 - Processing" },
{
value: 103,
label: "103 - Early Hints",
},
{ value: 200, label: "200 - OK" },
{ value: 201, label: "201 - Created" },
{ value: 202, label: "202 - Accepted" },
{ value: 203, label: "203 - Non-Authoritative Information" },
{ value: 204, label: "204 - No Content" },
{ value: 205, label: "205 - Reset Content" },
{ value: 206, label: "206 - Partial Content" },
{ value: 207, label: "207 - Multi-Status" },
{ value: 208, label: "208 - Already Reported" },
{ value: 218, label: "218 - This is fine (Apache Web Server)" },
{ value: 226, label: "226 - IM Used" },
{ value: 300, label: "300 - Multiple Choices" },
{ value: 301, label: "301 - Moved Permanently" },
{ value: 302, label: "302 - Found" },
{ value: 303, label: "303 - See Other" },
{ value: 304, label: "304 - Not Modified" },
{ value: 305, label: "305 - Use Proxy" },
{ value: 306, label: "306 - Switch Proxy" },
{ value: 307, label: "307 - Temporary Redirect" },
{ value: 308, label: "308 - Permanent Redirect" },
{ value: 400, label: "400 - Bad Request" },
{ value: 401, label: "401 - Unauthorized" },
{ value: 402, label: "402 - Payment Required" },
{ value: 403, label: "403 - Forbidden" },
{ value: 404, label: "404 - Not Found" },
{ value: 405, label: "405 - Method Not Allowed" },
{ value: 406, label: "406 - Not Acceptable" },
{ value: 407, label: "407 - Proxy Authentication Required" },
{ value: 408, label: "408 - Request Timeout" },
{ value: 409, label: "409 - Conflict" },
{ value: 410, label: "410 - Gone" },
{ value: 411, label: "411 - Length Required" },
{ value: 412, label: "412 - Precondition Failed" },
{ value: 413, label: "413 - Payload Too Large" },
{ value: 414, label: "414 - URI Too Long" },
{ value: 415, label: "415 - Unsupported Media Type" },
{ value: 416, label: "416 - Range Not Satisfiable" },
{ value: 417, label: "417 - Expectation Failed" },
{ value: 418, label: "418 - I'm A Teapot" },
{ value: 419, label: "419 - Page Expired (Laravel Framework)" },
{ value: 420, label: "420 - Method Failure (Spring Framework)" },
{ value: 421, label: "421 - Misdirected Request" },
{ value: 422, label: "422 - Unprocessable Entity" },
{ value: 423, label: "423 - Locked" },
{ value: 424, label: "424 - Failed Dependency" },
{ value: 425, label: "425 - Too Early" },
{ value: 426, label: "426 - Upgrade Required" },
{ value: 428, label: "428 - Precondition Required" },
{ value: 429, label: "429 - Too Many Requests" },
{
value: 430,
label: "430 - Request Header Fields Too Large (Shopify)",
},
{ value: 431, label: "431 - Request Header Fields Too Large" },
{ value: 440, label: "440 - Login Time-out (IIS)" },
{ value: 444, label: "444 - No Response (nginx)" },
{ value: 449, label: "449 - Retry With (IIS)" },
{
value: 450,
label: "450 - Blocked by Windows Parental Controls (Microsoft)",
},
{ value: 451, label: "451 - Unavailable For Legal Reasons" },
{
value: 460,
label: "460 - Client closed connection (AWS ELB)",
},
{ value: 494, label: "494 - Request Header Too Large (nginx)" },
{ value: 495, label: "495 - SSL Certificate Error (nginx)" },
{ value: 496, label: "496 - SSL Certificate Required (nginx)" },
{
value: 497,
label: "497 - HTTP Request Sent to HTTPS Port (nginx)",
},
{ value: 499, label: "499 - Client Closed Request (nginx)" },
{ value: 500, label: "500 - Internal Server Error" },
{ value: 501, label: "501 - Not Implemented" },
{ value: 502, label: "502 - Bad Gateway" },
{ value: 503, label: "503 - Service Unavailable" },
{ value: 504, label: "504 - Gateway Timeout" },
{ value: 505, label: "505 - HTTP Version Not Supported" },
{ value: 506, label: "506 - Variant Also Negotiates" },
{ value: 507, label: "507 - Insufficient Storage" },
{ value: 508, label: "508 - Loop Detected" },
{
value: 509,
label: "509 - Bandwidth Limit Exceeded (Apache Web Server)",
},
{ value: 510, label: "510 - Not Extended" },
{ value: 511, label: "511 - Network Authentication Required" },
{
value: 520,
label: "520 - Web Server Returned An Unknown Error (Cloudflare)",
},
{ value: 521, label: "521 - Web Server Is Down (Cloudflare)" },
{ value: 522, label: "522 - Connection Timed Out (Cloudflare)" },
{
value: 523,
label: "523 - Origin Is Unreachable (Cloudflare)",
},
{ value: 524, label: "524 - A Timeout Occurred (Cloudflare)" },
{ value: 525, label: "525 - SSL Handshake Failed (Cloudflare)" },
{
value: 526,
label: "526 - Invalid SSL Certificate (Cloudflare)",
},
{ value: 527, label: "527 - Railgun Error (Cloudflare)" },
{ value: 561, label: "561 - Unauthorized (AWS ELB)" },
]
28 changes: 28 additions & 0 deletions src/features/RouteEditPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { H2 } from "@/shared/ui/typography"
import { Button } from "@/shared/ui/button"
import { Separator } from "@/shared/ui/separator"

import { CreateEditForm } from "./CreateEditForm"
import { useEditorRouteState } from "@/providers/useMswDevtoolsContext"

export const RouteEditPanel = () => {
const { onCloseEditPanel } = useEditorRouteState()

return (
<div className="flex flex-col w-full">
<header className="flex justify-between align-items px-[16px] py-[12px]">
<H2>Create Route</H2>
<Button
variant="ghost"
onClick={() => {
onCloseEditPanel()
}}
>
X
</Button>
</header>
<Separator />
<CreateEditForm />
</div>
)
}
Loading

0 comments on commit db736a2

Please sign in to comment.