CoolFace
Apppublic

Leon4gr45/builder

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes
scheduled-function-editor.tsx257 linesDownload Raw Back to database-manager
1'use client';2 3import React, { useState, useEffect } from 'react';4import { ScheduledFunction, EdgeFunction } from '@/lib/vfs/types';5import {6  Dialog,7  DialogContent,8  DialogDescription,9  DialogHeader,10  DialogTitle,11} from '@/components/ui/dialog';12import { Button } from '@/components/ui/button';13import { Input } from '@/components/ui/input';14import { Label } from '@/components/ui/label';15import { Textarea } from '@/components/ui/textarea';16import {17  Select,18  SelectContent,19  SelectItem,20  SelectTrigger,21  SelectValue,22} from '@/components/ui/select';23import { Loader2, AlertCircle, Info } from 'lucide-react';24 25interface ScheduledFunctionEditorProps {26  scheduledFunction: ScheduledFunction | null;27  edgeFunctions: EdgeFunction[];28  isOpen: boolean;29  onClose: () => void;30  onSave: (data: Partial<ScheduledFunction>) => Promise<void>;31}32 33export function ScheduledFunctionEditor({34  scheduledFunction: fn,35  edgeFunctions,36  isOpen,37  onClose,38  onSave,39}: ScheduledFunctionEditorProps) {40  const [name, setName] = useState(fn?.name || '');41  const [functionId, setFunctionId] = useState(fn?.functionId || '');42  const [cronExpression, setCronExpression] = useState(fn?.cronExpression || '');43  const [timezone, setTimezone] = useState(fn?.timezone || 'UTC');44  const [description, setDescription] = useState(fn?.description || '');45  const [config, setConfig] = useState(fn?.config ? JSON.stringify(fn.config, null, 2) : '{}');46  const [saving, setSaving] = useState(false);47  const [error, setError] = useState<string | null>(null);48 49  useEffect(() => {50    if (isOpen) {51      setName(fn?.name || '');52      setFunctionId(fn?.functionId || '');53      setCronExpression(fn?.cronExpression || '');54      setTimezone(fn?.timezone || 'UTC');55      setDescription(fn?.description || '');56      setConfig(fn?.config ? JSON.stringify(fn.config, null, 2) : '{}');57      setError(null);58    }59  }, [fn, isOpen]);60 61  const handleSave = async () => {62    setError(null);63 64    if (!name.trim()) {65      setError('Name is required');66      return;67    }68    if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {69      setError('Name must be lowercase letters, numbers, and hyphens only');70      return;71    }72    if (!functionId) {73      setError('Edge function selection is required');74      return;75    }76    if (!cronExpression.trim()) {77      setError('Cron expression is required');78      return;79    }80 81    let parsedConfig: Record<string, unknown> = {};82    if (config.trim()) {83      try {84        const parsed = JSON.parse(config);85        if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {86          setError('Config must be a JSON object');87          return;88        }89        parsedConfig = parsed;90      } catch {91        setError('Config must be valid JSON');92        return;93      }94    }95 96    setSaving(true);97    try {98      await onSave({99        name: name.trim(),100        functionId,101        cronExpression: cronExpression.trim(),102        timezone: timezone.trim() || 'UTC',103        description: description.trim() || undefined,104        config: parsedConfig,105        enabled: fn?.enabled ?? true,106      });107    } catch (err) {108      setError(err instanceof Error ? err.message : 'Failed to save scheduled function');109    } finally {110      setSaving(false);111    }112  };113 114  return (115    <Dialog open={isOpen} onOpenChange={onClose}>116      <DialogContent className="sm:max-w-lg max-h-[85vh] flex flex-col">117        <DialogHeader>118          <DialogTitle>119            {fn ? 'Edit Schedule' : 'Create Schedule'}120          </DialogTitle>121          <DialogDescription>122            Run an edge function on a cron schedule.123          </DialogDescription>124        </DialogHeader>125 126        <div className="flex-1 overflow-auto space-y-4">127          {/* Name */}128          <div className="space-y-2">129            <Label htmlFor="sched-name">Name</Label>130            <Input131              id="sched-name"132              value={name}133              onChange={e => setName(e.target.value.toLowerCase())}134              placeholder="daily-report"135              disabled={!!fn}136            />137          </div>138 139          {/* Edge Function */}140          <div className="space-y-2">141            <Label htmlFor="sched-function">Edge Function</Label>142            <Select value={functionId} onValueChange={setFunctionId}>143              <SelectTrigger>144                <SelectValue placeholder="Select a function..." />145              </SelectTrigger>146              <SelectContent>147                {edgeFunctions.map(ef => (148                  <SelectItem key={ef.id} value={ef.id}>149                    {ef.name}150                  </SelectItem>151                ))}152              </SelectContent>153            </Select>154            {edgeFunctions.length === 0 && (155              <p className="text-xs text-muted-foreground">156                No edge functions available. Create one in the Functions tab first.157              </p>158            )}159          </div>160 161          {/* Cron Expression */}162          <div className="space-y-2">163            <Label htmlFor="sched-cron">Cron Expression</Label>164            <Input165              id="sched-cron"166              value={cronExpression}167              onChange={e => setCronExpression(e.target.value)}168              placeholder="0 8 * * *"169              className="font-mono"170            />171          </div>172 173          {/* Timezone */}174          <div className="space-y-2">175            <Label htmlFor="sched-tz">Timezone</Label>176            <Input177              id="sched-tz"178              value={timezone}179              onChange={e => setTimezone(e.target.value)}180              placeholder="UTC"181            />182            <p className="text-xs text-muted-foreground">183              e.g. UTC, America/New_York, Europe/London184            </p>185          </div>186 187          {/* Description */}188          <div className="space-y-2">189            <Label htmlFor="sched-desc">Description (optional)</Label>190            <Input191              id="sched-desc"192              value={description}193              onChange={e => setDescription(e.target.value)}194              placeholder="What does this schedule do?"195            />196          </div>197 198          {/* Config */}199          <div className="space-y-2">200            <Label htmlFor="sched-config">Config JSON (optional)</Label>201            <Textarea202              id="sched-config"203              value={config}204              onChange={e => setConfig(e.target.value)}205              placeholder="{}"206              className="font-mono text-sm h-20"207            />208            <p className="text-xs text-muted-foreground">209              Custom data passed as the request body to the edge function.210            </p>211          </div>212 213          {/* Cron Reference */}214          <div className="bg-muted/30 border rounded-lg p-4 space-y-2">215            <div className="flex items-center gap-2 text-sm font-medium">216              <Info className="h-4 w-4" />217              Cron Patterns <span className="font-normal text-muted-foreground">(minimum 5 min interval)</span>218            </div>219            <div className="grid gap-1 text-xs font-mono">220              <div><span className="text-muted-foreground">*/5 * * * *</span>  Every 5 minutes</div>221              <div><span className="text-muted-foreground">0 * * * *</span>    Every hour</div>222              <div><span className="text-muted-foreground">0 8 * * *</span>    Daily at 8am</div>223              <div><span className="text-muted-foreground">0 0 * * 1</span>    Every Monday at midnight</div>224              <div><span className="text-muted-foreground">0 0 1 * *</span>    First of every month</div>225            </div>226          </div>227 228          {/* Error */}229          {error && (230            <div className="flex items-center gap-2 text-sm text-destructive bg-destructive/10 p-3 rounded-lg">231              <AlertCircle className="h-4 w-4 shrink-0" />232              {error}233            </div>234          )}235        </div>236 237        {/* Footer */}238        <div className="flex items-center justify-end gap-2 pt-4 border-t">239          <Button variant="outline" onClick={onClose} disabled={saving}>240            Cancel241          </Button>242          <Button onClick={handleSave} disabled={saving}>243            {saving ? (244              <>245                <Loader2 className="h-4 w-4 mr-2 animate-spin" />246                Saving...247              </>248            ) : (249              fn ? 'Save Changes' : 'Create Schedule'250            )}251          </Button>252        </div>253      </DialogContent>254    </Dialog>255  );256}257