CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
function-editor.tsx261 linesDownload Raw Back to database-manager
1'use client';2 3import React, { useState, useEffect } from 'react';4import MonacoEditor from '@monaco-editor/react';5import { EdgeFunction } from '@/lib/vfs/types';6import {7  Dialog,8  DialogContent,9  DialogDescription,10  DialogHeader,11  DialogTitle,12} from '@/components/ui/dialog';13import { Button } from '@/components/ui/button';14import { Input } from '@/components/ui/input';15import { Label } from '@/components/ui/label';16import {17  Select,18  SelectContent,19  SelectItem,20  SelectTrigger,21  SelectValue,22} from '@/components/ui/select';23import { Loader2, AlertCircle, Info } from 'lucide-react';24import { useTheme } from 'next-themes';25 26interface FunctionEditorProps {27  deploymentId: string;28  function: EdgeFunction | null;29  isOpen: boolean;30  onClose: () => void;31  onSave: (data: Partial<EdgeFunction>) => Promise<void>;32}33 34const DEFAULT_CODE = `// Access the request object35// request.method - HTTP method36// request.body - Parsed request body37// request.query - Query string parameters38// request.headers - Request headers39 40// Use the database41// db.query(sql, params) - Execute SELECT query42// db.run(sql, params) - Execute INSERT/UPDATE/DELETE43// db.all(sql, params) - Alias for query44 45// Return a response46// Response.json(data, status) - Return JSON47// Response.text(text, status) - Return text48// Response.error(message, status) - Return error49 50// Example: List items51const items = db.all('SELECT * FROM items LIMIT 10');52Response.json({ items });53`;54 55export function FunctionEditor({56  deploymentId,57  function: fn,58  isOpen,59  onClose,60  onSave,61}: FunctionEditorProps) {62  const [name, setName] = useState(fn?.name || '');63  const [description, setDescription] = useState(fn?.description || '');64  const [method, setMethod] = useState<EdgeFunction['method']>(fn?.method || 'ANY');65  const [code, setCode] = useState(fn?.code || DEFAULT_CODE);66  const [timeoutMs, setTimeoutMs] = useState(fn?.timeoutMs || 5000);67  const [saving, setSaving] = useState(false);68  const [error, setError] = useState<string | null>(null);69  const { resolvedTheme } = useTheme();70  const [mounted, setMounted] = useState(false);71 72  useEffect(() => {73    setMounted(true);74  }, []);75 76  useEffect(() => {77    if (isOpen) {78      setName(fn?.name || '');79      setDescription(fn?.description || '');80      setMethod(fn?.method || 'ANY');81      setCode(fn?.code || DEFAULT_CODE);82      setTimeoutMs(fn?.timeoutMs || 5000);83      setError(null);84    }85  }, [fn, isOpen]);86 87  const handleSave = async () => {88    setError(null);89 90    // Basic validation91    if (!name.trim()) {92      setError('Function name is required');93      return;94    }95    if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {96      setError('Name must be lowercase letters, numbers, and hyphens only');97      return;98    }99    if (!code.trim()) {100      setError('Function code is required');101      return;102    }103 104    setSaving(true);105    try {106      await onSave({107        name: name.trim(),108        description: description.trim() || undefined,109        method,110        code,111        timeoutMs,112        enabled: fn?.enabled ?? true,113      });114    } catch (err) {115      setError(err instanceof Error ? err.message : 'Failed to save function');116    } finally {117      setSaving(false);118    }119  };120 121  if (!mounted) return null;122 123  return (124    <Dialog open={isOpen} onOpenChange={onClose}>125      <DialogContent className="sm:max-w-3xl h-[85vh] flex flex-col">126        <DialogHeader>127          <DialogTitle>128            {fn ? 'Edit Function' : 'Create Function'}129          </DialogTitle>130          <DialogDescription>131            Define an HTTP endpoint that can access your deployment database.132          </DialogDescription>133        </DialogHeader>134 135        <div className="flex-1 overflow-auto space-y-4">136          {/* Name & Method */}137          <div className="grid grid-cols-3 gap-4">138            <div className="col-span-2 space-y-2">139              <Label htmlFor="name">Function Name</Label>140              <Input141                id="name"142                value={name}143                onChange={e => setName(e.target.value.toLowerCase())}144                placeholder="my-function"145                disabled={!!fn}146              />147              {deploymentId && (148                <p className="text-xs text-muted-foreground">149                  URL: /api/deployments/{deploymentId}/functions/<span className="font-mono">{name || 'name'}</span>150                </p>151              )}152            </div>153            <div className="space-y-2">154              <Label htmlFor="method">HTTP Method</Label>155              <Select value={method} onValueChange={v => setMethod(v as EdgeFunction['method'])}>156                <SelectTrigger>157                  <SelectValue />158                </SelectTrigger>159                <SelectContent>160                  <SelectItem value="ANY">ANY</SelectItem>161                  <SelectItem value="GET">GET</SelectItem>162                  <SelectItem value="POST">POST</SelectItem>163                  <SelectItem value="PUT">PUT</SelectItem>164                  <SelectItem value="DELETE">DELETE</SelectItem>165                </SelectContent>166              </Select>167            </div>168          </div>169 170          {/* Description */}171          <div className="space-y-2">172            <Label htmlFor="description">Description (optional)</Label>173            <Input174              id="description"175              value={description}176              onChange={e => setDescription(e.target.value)}177              placeholder="What does this function do?"178            />179          </div>180 181          {/* Timeout */}182          <div className="space-y-2">183            <Label htmlFor="timeout">Timeout (seconds)</Label>184            <div className="flex items-center gap-2">185              <Input186                id="timeout"187                type="number"188                min={1}189                max={30}190                value={timeoutMs / 1000}191                onChange={e => setTimeoutMs(Math.min(30, Math.max(1, parseInt(e.target.value) || 5)) * 1000)}192                className="w-24"193              />194              <span className="text-sm text-muted-foreground">1-30 seconds</span>195            </div>196          </div>197 198          {/* Code Editor */}199          <div className="space-y-2">200            <Label>Function Code</Label>201            <div className="h-64 border rounded-lg overflow-hidden">202              <MonacoEditor203                language="javascript"204                theme={resolvedTheme === 'dark' ? 'vs-dark' : 'light'}205                value={code}206                onChange={value => setCode(value || '')}207                options={{208                  minimap: { enabled: false },209                  fontSize: 13,210                  scrollBeyondLastLine: false,211                  automaticLayout: true,212                  tabSize: 2,213                }}214              />215            </div>216          </div>217 218          {/* API Reference */}219          <div className="bg-muted/30 border rounded-lg p-4 space-y-2">220            <div className="flex items-center gap-2 text-sm font-medium">221              <Info className="h-4 w-4" />222              Available APIs223            </div>224            <div className="grid gap-2 text-xs font-mono">225              <div><span className="text-blue-500">request</span>.method, .body, .query, .headers, .params, .path</div>226              <div><span className="text-green-500">db</span>.query(sql, params), .run(sql, params), .all(sql, params)</div>227              <div><span className="text-purple-500">Response</span>.json(data, status), .text(text, status), .error(msg, status)</div>228              <div><span className="text-yellow-500">fetch</span>(url, options) - External HTTP requests</div>229            </div>230          </div>231 232          {/* Error */}233          {error && (234            <div className="flex items-center gap-2 text-sm text-destructive bg-destructive/10 p-3 rounded-lg">235              <AlertCircle className="h-4 w-4" />236              {error}237            </div>238          )}239        </div>240 241        {/* Footer */}242        <div className="flex items-center justify-end gap-2 pt-4 border-t">243          <Button variant="outline" onClick={onClose} disabled={saving}>244            Cancel245          </Button>246          <Button onClick={handleSave} disabled={saving}>247            {saving ? (248              <>249                <Loader2 className="h-4 w-4 mr-2 animate-spin" />250                Saving...251              </>252            ) : (253              fn ? 'Save Changes' : 'Create Function'254            )}255          </Button>256        </div>257      </DialogContent>258    </Dialog>259  );260}261