builder / components /publish-settings /scripts-tab.tsx
Leon4gr45's picture
Upload folder using huggingface_hub
eeb9404 verified
Raw
History Blame Contribute Delete
12.1 kB
'use client';
import React, { useState } from 'react';
import { PublishSettings, ScriptConfig } from '@/lib/vfs/types';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Plus, Edit, Trash2, Code } from 'lucide-react';
interface ScriptsTabProps {
settings: PublishSettings;
onChange: (settings: PublishSettings) => void;
}
export function ScriptsTab({ settings, onChange }: ScriptsTabProps) {
const [editingScript, setEditingScript] = useState<ScriptConfig | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [scriptPosition, setScriptPosition] = useState<'head' | 'body'>('head');
const allScripts = [
...settings.headScripts.map(s => ({ ...s, position: 'head' as const })),
...settings.bodyScripts.map(s => ({ ...s, position: 'body' as const })),
];
const handleAddScript = () => {
const newScript: ScriptConfig = {
id: `script-${Date.now()}`,
name: '',
content: '',
type: 'inline',
enabled: true,
};
setEditingScript(newScript);
setScriptPosition('head');
setIsDialogOpen(true);
};
const handleEditScript = (script: ScriptConfig, position: 'head' | 'body') => {
setEditingScript(script);
setScriptPosition(position);
setIsDialogOpen(true);
};
const handleDeleteScript = (scriptId: string) => {
if (!confirm('Are you sure you want to delete this script?')) return;
onChange({
...settings,
headScripts: settings.headScripts.filter(s => s.id !== scriptId),
bodyScripts: settings.bodyScripts.filter(s => s.id !== scriptId),
});
};
const handleToggleScript = (scriptId: string, position: 'head' | 'body') => {
const scripts = position === 'head' ? settings.headScripts : settings.bodyScripts;
const updatedScripts = scripts.map(s =>
s.id === scriptId ? { ...s, enabled: !s.enabled } : s
);
onChange({
...settings,
[position === 'head' ? 'headScripts' : 'bodyScripts']: updatedScripts,
});
};
const handleSaveScript = () => {
if (!editingScript || !editingScript.name.trim()) {
alert('Please provide a name for the script');
return;
}
const targetArray = scriptPosition === 'head' ? settings.headScripts : settings.bodyScripts;
const otherArray = scriptPosition === 'head' ? settings.bodyScripts : settings.headScripts;
// Check if editing existing or creating new
const existingIndex = targetArray.findIndex(s => s.id === editingScript.id);
let updatedScripts;
if (existingIndex >= 0) {
// Update existing
updatedScripts = [...targetArray];
updatedScripts[existingIndex] = editingScript;
} else {
// Check if it exists in the other position
const existsInOther = otherArray.some(s => s.id === editingScript.id);
if (existsInOther) {
// Move from other position to current
const filtered = otherArray.filter(s => s.id !== editingScript.id);
onChange({
...settings,
[scriptPosition === 'head' ? 'headScripts' : 'bodyScripts']: [...targetArray, editingScript],
[scriptPosition === 'head' ? 'bodyScripts' : 'headScripts']: filtered,
});
setIsDialogOpen(false);
setEditingScript(null);
return;
} else {
// Add new
updatedScripts = [...targetArray, editingScript];
}
}
onChange({
...settings,
[scriptPosition === 'head' ? 'headScripts' : 'bodyScripts']: updatedScripts,
});
setIsDialogOpen(false);
setEditingScript(null);
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">Script Management</h3>
<p className="text-sm text-muted-foreground">
Add custom scripts to your published deployment
</p>
</div>
<Button onClick={handleAddScript} size="sm">
<Plus className="h-4 w-4 mr-2" />
Add Script
</Button>
</div>
{allScripts.length === 0 ? (
<div className="text-center p-8 border-2 border-dashed rounded-lg">
<Code className="h-12 w-12 mx-auto text-muted-foreground mb-3" />
<h3 className="text-lg font-semibold mb-2">No Scripts Added</h3>
<p className="text-sm text-muted-foreground mb-4">
Add tracking scripts, analytics, or custom code to your deployment
</p>
<Button onClick={handleAddScript} variant="outline">
<Plus className="h-4 w-4 mr-2" />
Add Your First Script
</Button>
</div>
) : (
<div className="space-y-4">
{allScripts.map((script) => (
<div
key={script.id}
className="flex items-start gap-4 p-4 border rounded-lg hover:bg-accent/50 transition-colors"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h4 className="font-semibold truncate">{script.name}</h4>
<Badge variant={script.position === 'head' ? 'default' : 'secondary'}>
{script.position === 'head' ? '<head>' : 'before </body>'}
</Badge>
<Badge variant="outline">
{script.type}
</Badge>
{script.async && <Badge variant="outline">async</Badge>}
{script.defer && <Badge variant="outline">defer</Badge>}
</div>
<p className="text-sm text-muted-foreground truncate">
{script.type === 'inline'
? `${script.content.length} characters`
: script.content}
</p>
</div>
<div className="flex items-center gap-2">
<Switch
checked={script.enabled}
onCheckedChange={() => handleToggleScript(script.id, script.position)}
/>
<Button
variant="ghost"
size="sm"
onClick={() => handleEditScript(script, script.position)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteScript(script.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
{/* Script Editor Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>
{editingScript?.name ? 'Edit Script' : 'Add Script'}
</DialogTitle>
<DialogDescription>
Configure a custom script to inject into your published deployment
</DialogDescription>
</DialogHeader>
{editingScript && (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="script-name">Script Name</Label>
<Input
id="script-name"
placeholder="e.g., Google Analytics"
value={editingScript.name}
onChange={(e) =>
setEditingScript({ ...editingScript, name: e.target.value })
}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="script-position">Position</Label>
<Select
value={scriptPosition}
onValueChange={(value: 'head' | 'body') => setScriptPosition(value)}
>
<SelectTrigger id="script-position">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="head">In &lt;head&gt;</SelectItem>
<SelectItem value="body">Before &lt;/body&gt;</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="script-type">Type</Label>
<Select
value={editingScript.type}
onValueChange={(value: 'inline' | 'external') =>
setEditingScript({ ...editingScript, type: value })
}
>
<SelectTrigger id="script-type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="inline">Inline Script</SelectItem>
<SelectItem value="external">External URL</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="script-content">
{editingScript.type === 'inline' ? 'Script Code' : 'Script URL'}
</Label>
{editingScript.type === 'inline' ? (
<Textarea
id="script-content"
placeholder="<script>...</script>"
rows={8}
value={editingScript.content}
onChange={(e) =>
setEditingScript({ ...editingScript, content: e.target.value })
}
className="font-mono text-sm"
/>
) : (
<Input
id="script-content"
type="url"
placeholder="https://example.com/script.js"
value={editingScript.content}
onChange={(e) =>
setEditingScript({ ...editingScript, content: e.target.value })
}
/>
)}
</div>
{editingScript.type === 'external' && (
<div className="flex gap-4">
<div className="flex items-center space-x-2">
<Switch
id="script-async"
checked={editingScript.async || false}
onCheckedChange={(checked) =>
setEditingScript({ ...editingScript, async: checked })
}
/>
<Label htmlFor="script-async">Async</Label>
</div>
<div className="flex items-center space-x-2">
<Switch
id="script-defer"
checked={editingScript.defer || false}
onCheckedChange={(checked) =>
setEditingScript({ ...editingScript, defer: checked })
}
/>
<Label htmlFor="script-defer">Defer</Label>
</div>
</div>
)}
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleSaveScript}>Save Script</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}