mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
refactor: extract json editor ui components
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui'
|
||||
import { Button } from '@/components/ui'
|
||||
import { Alert, AlertDescription } from '@/components/ui'
|
||||
import { FloppyDisk, X, Warning, ShieldCheck } from '@phosphor-icons/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Alert, AlertDescription, Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui'
|
||||
import { Warning } from '@phosphor-icons/react'
|
||||
import Editor from '@monaco-editor/react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { SchemaSection } from './json/SchemaSection'
|
||||
import { Toolbar } from './json/Toolbar'
|
||||
import { securityScanner, type SecurityScanResult } from '@/lib/security-scanner'
|
||||
import { SecurityWarningDialog } from '@/components/organisms/security/SecurityWarningDialog'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface JsonEditorProps {
|
||||
open: boolean
|
||||
@@ -32,10 +33,12 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
}
|
||||
}, [open, value])
|
||||
|
||||
const parseJson = () => JSON.parse(jsonText)
|
||||
|
||||
const handleSave = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText)
|
||||
|
||||
const parsed = parseJson()
|
||||
|
||||
const scanResult = securityScanner.scanJSON(jsonText)
|
||||
setSecurityScanResult(scanResult)
|
||||
|
||||
@@ -66,8 +69,7 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
|
||||
const handleForceSave = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText)
|
||||
onSave(parsed)
|
||||
onSave(parseJson())
|
||||
setError(null)
|
||||
setPendingSave(false)
|
||||
setShowSecurityDialog(false)
|
||||
@@ -81,7 +83,7 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
const scanResult = securityScanner.scanJSON(jsonText)
|
||||
setSecurityScanResult(scanResult)
|
||||
setShowSecurityDialog(true)
|
||||
|
||||
|
||||
if (scanResult.safe) {
|
||||
toast.success('No security issues detected')
|
||||
} else {
|
||||
@@ -91,8 +93,7 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
|
||||
const handleFormat = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText)
|
||||
setJsonText(JSON.stringify(parsed, null, 2))
|
||||
setJsonText(JSON.stringify(parseJson(), null, 2))
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Invalid JSON - cannot format')
|
||||
@@ -106,7 +107,7 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl">{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
<div className="space-y-4">
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
@@ -115,16 +116,21 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{securityScanResult && securityScanResult.severity !== 'safe' && securityScanResult.severity !== 'low' && !showSecurityDialog && (
|
||||
<Alert className="border-yellow-200 bg-yellow-50">
|
||||
<Warning className="h-5 w-5 text-yellow-600" weight="fill" />
|
||||
<AlertDescription className="text-yellow-800">
|
||||
{securityScanResult.issues.length} security {securityScanResult.issues.length === 1 ? 'issue' : 'issues'} detected.
|
||||
Click Security Scan to review.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{securityScanResult &&
|
||||
securityScanResult.severity !== 'safe' &&
|
||||
securityScanResult.severity !== 'low' &&
|
||||
!showSecurityDialog && (
|
||||
<Alert className="border-yellow-200 bg-yellow-50">
|
||||
<Warning className="h-5 w-5 text-yellow-600" weight="fill" />
|
||||
<AlertDescription className="text-yellow-800">
|
||||
{securityScanResult.issues.length} security {securityScanResult.issues.length === 1 ? 'issue' : 'issues'}
|
||||
detected. Click Security Scan to review.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<SchemaSection schema={schema} />
|
||||
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<Editor
|
||||
height="600px"
|
||||
@@ -157,23 +163,12 @@ export function JsonEditor({ open, onClose, title, value, onSave, schema }: Json
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={handleScan}>
|
||||
<ShieldCheck className="mr-2" />
|
||||
Security Scan
|
||||
</Button>
|
||||
<Button variant="outline" onClick={handleFormat}>
|
||||
Format JSON
|
||||
</Button>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
<X className="mr-2" />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSave} className="bg-accent text-accent-foreground hover:bg-accent/90">
|
||||
<FloppyDisk className="mr-2" />
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
<Toolbar
|
||||
onScan={handleScan}
|
||||
onFormat={handleFormat}
|
||||
onCancel={onClose}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui'
|
||||
|
||||
interface SchemaSectionProps {
|
||||
schema?: unknown
|
||||
}
|
||||
|
||||
export function SchemaSection({ schema }: SchemaSectionProps) {
|
||||
if (!schema) return null
|
||||
|
||||
const formattedSchema =
|
||||
typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col gap-1">
|
||||
<CardTitle>Schema</CardTitle>
|
||||
<CardDescription>Reference for the expected JSON structure</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<pre className="max-h-48 overflow-auto rounded border bg-muted px-3 py-2 text-xs leading-5 whitespace-pre-wrap">
|
||||
{formattedSchema}
|
||||
</pre>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
31
frontends/nextjs/src/components/editors/json/Toolbar.tsx
Normal file
31
frontends/nextjs/src/components/editors/json/Toolbar.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Button, DialogFooter } from '@/components/ui'
|
||||
import { FloppyDisk, ShieldCheck, X } from '@phosphor-icons/react'
|
||||
|
||||
interface ToolbarProps {
|
||||
onScan: () => void
|
||||
onFormat: () => void
|
||||
onCancel: () => void
|
||||
onSave: () => void
|
||||
}
|
||||
|
||||
export function Toolbar({ onScan, onFormat, onCancel, onSave }: ToolbarProps) {
|
||||
return (
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={onScan}>
|
||||
<ShieldCheck className="mr-2" />
|
||||
Security Scan
|
||||
</Button>
|
||||
<Button variant="outline" onClick={onFormat}>
|
||||
Format JSON
|
||||
</Button>
|
||||
<Button variant="outline" onClick={onCancel}>
|
||||
<X className="mr-2" />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={onSave} className="bg-accent text-accent-foreground hover:bg-accent/90">
|
||||
<FloppyDisk className="mr-2" />
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user