Leon4gr45/builder
0
1'use client';2 3import React, { useState } from 'react';4import { PublishSettings, ScriptConfig } from '@/lib/vfs/types';5import { Button } from '@/components/ui/button';6import { Input } from '@/components/ui/input';7import { Label } from '@/components/ui/label';8import { Textarea } from '@/components/ui/textarea';9import { Switch } from '@/components/ui/switch';10import { Badge } from '@/components/ui/badge';11import {12 Dialog,13 DialogContent,14 DialogDescription,15 DialogHeader,16 DialogTitle,17 DialogFooter,18} from '@/components/ui/dialog';19import {20 Select,21 SelectContent,22 SelectItem,23 SelectTrigger,24 SelectValue,25} from '@/components/ui/select';26import { Plus, Edit, Trash2, Code } from 'lucide-react';27 28interface ScriptsTabProps {29 settings: PublishSettings;30 onChange: (settings: PublishSettings) => void;31}32 33export function ScriptsTab({ settings, onChange }: ScriptsTabProps) {34 const [editingScript, setEditingScript] = useState<ScriptConfig | null>(null);35 const [isDialogOpen, setIsDialogOpen] = useState(false);36 const [scriptPosition, setScriptPosition] = useState<'head' | 'body'>('head');37 38 const allScripts = [39 ...settings.headScripts.map(s => ({ ...s, position: 'head' as const })),40 ...settings.bodyScripts.map(s => ({ ...s, position: 'body' as const })),41 ];42 43 const handleAddScript = () => {44 const newScript: ScriptConfig = {45 id: `script-${Date.now()}`,46 name: '',47 content: '',48 type: 'inline',49 enabled: true,50 };51 setEditingScript(newScript);52 setScriptPosition('head');53 setIsDialogOpen(true);54 };55 56 const handleEditScript = (script: ScriptConfig, position: 'head' | 'body') => {57 setEditingScript(script);58 setScriptPosition(position);59 setIsDialogOpen(true);60 };61 62 const handleDeleteScript = (scriptId: string) => {63 if (!confirm('Are you sure you want to delete this script?')) return;64 65 onChange({66 ...settings,67 headScripts: settings.headScripts.filter(s => s.id !== scriptId),68 bodyScripts: settings.bodyScripts.filter(s => s.id !== scriptId),69 });70 };71 72 const handleToggleScript = (scriptId: string, position: 'head' | 'body') => {73 const scripts = position === 'head' ? settings.headScripts : settings.bodyScripts;74 const updatedScripts = scripts.map(s =>75 s.id === scriptId ? { ...s, enabled: !s.enabled } : s76 );77 78 onChange({79 ...settings,80 [position === 'head' ? 'headScripts' : 'bodyScripts']: updatedScripts,81 });82 };83 84 const handleSaveScript = () => {85 if (!editingScript || !editingScript.name.trim()) {86 alert('Please provide a name for the script');87 return;88 }89 90 const targetArray = scriptPosition === 'head' ? settings.headScripts : settings.bodyScripts;91 const otherArray = scriptPosition === 'head' ? settings.bodyScripts : settings.headScripts;92 93 // Check if editing existing or creating new94 const existingIndex = targetArray.findIndex(s => s.id === editingScript.id);95 let updatedScripts;96 97 if (existingIndex >= 0) {98 // Update existing99 updatedScripts = [...targetArray];100 updatedScripts[existingIndex] = editingScript;101 } else {102 // Check if it exists in the other position103 const existsInOther = otherArray.some(s => s.id === editingScript.id);104 if (existsInOther) {105 // Move from other position to current106 const filtered = otherArray.filter(s => s.id !== editingScript.id);107 onChange({108 ...settings,109 [scriptPosition === 'head' ? 'headScripts' : 'bodyScripts']: [...targetArray, editingScript],110 [scriptPosition === 'head' ? 'bodyScripts' : 'headScripts']: filtered,111 });112 setIsDialogOpen(false);113 setEditingScript(null);114 return;115 } else {116 // Add new117 updatedScripts = [...targetArray, editingScript];118 }119 }120 121 onChange({122 ...settings,123 [scriptPosition === 'head' ? 'headScripts' : 'bodyScripts']: updatedScripts,124 });125 126 setIsDialogOpen(false);127 setEditingScript(null);128 };129 130 return (131 <div className="space-y-6">132 <div className="flex items-center justify-between">133 <div>134 <h3 className="text-lg font-semibold">Script Management</h3>135 <p className="text-sm text-muted-foreground">136 Add custom scripts to your published deployment137 </p>138 </div>139 <Button onClick={handleAddScript} size="sm">140 <Plus className="h-4 w-4 mr-2" />141 Add Script142 </Button>143 </div>144 145 {allScripts.length === 0 ? (146 <div className="text-center p-8 border-2 border-dashed rounded-lg">147 <Code className="h-12 w-12 mx-auto text-muted-foreground mb-3" />148 <h3 className="text-lg font-semibold mb-2">No Scripts Added</h3>149 <p className="text-sm text-muted-foreground mb-4">150 Add tracking scripts, analytics, or custom code to your deployment151 </p>152 <Button onClick={handleAddScript} variant="outline">153 <Plus className="h-4 w-4 mr-2" />154 Add Your First Script155 </Button>156 </div>157 ) : (158 <div className="space-y-4">159 {allScripts.map((script) => (160 <div161 key={script.id}162 className="flex items-start gap-4 p-4 border rounded-lg hover:bg-accent/50 transition-colors"163 >164 <div className="flex-1 min-w-0">165 <div className="flex items-center gap-2 mb-2">166 <h4 className="font-semibold truncate">{script.name}</h4>167 <Badge variant={script.position === 'head' ? 'default' : 'secondary'}>168 {script.position === 'head' ? '<head>' : 'before </body>'}169 </Badge>170 <Badge variant="outline">171 {script.type}172 </Badge>173 {script.async && <Badge variant="outline">async</Badge>}174 {script.defer && <Badge variant="outline">defer</Badge>}175 </div>176 <p className="text-sm text-muted-foreground truncate">177 {script.type === 'inline'178 ? `${script.content.length} characters`179 : script.content}180 </p>181 </div>182 <div className="flex items-center gap-2">183 <Switch184 checked={script.enabled}185 onCheckedChange={() => handleToggleScript(script.id, script.position)}186 />187 <Button188 variant="ghost"189 size="sm"190 onClick={() => handleEditScript(script, script.position)}191 >192 <Edit className="h-4 w-4" />193 </Button>194 <Button195 variant="ghost"196 size="sm"197 onClick={() => handleDeleteScript(script.id)}198 >199 <Trash2 className="h-4 w-4" />200 </Button>201 </div>202 </div>203 ))}204 </div>205 )}206 207 {/* Script Editor Dialog */}208 <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>209 <DialogContent className="max-w-2xl">210 <DialogHeader>211 <DialogTitle>212 {editingScript?.name ? 'Edit Script' : 'Add Script'}213 </DialogTitle>214 <DialogDescription>215 Configure a custom script to inject into your published deployment216 </DialogDescription>217 </DialogHeader>218 219 {editingScript && (220 <div className="space-y-4">221 <div className="space-y-2">222 <Label htmlFor="script-name">Script Name</Label>223 <Input224 id="script-name"225 placeholder="e.g., Google Analytics"226 value={editingScript.name}227 onChange={(e) =>228 setEditingScript({ ...editingScript, name: e.target.value })229 }230 />231 </div>232 233 <div className="grid grid-cols-2 gap-4">234 <div className="space-y-2">235 <Label htmlFor="script-position">Position</Label>236 <Select237 value={scriptPosition}238 onValueChange={(value: 'head' | 'body') => setScriptPosition(value)}239 >240 <SelectTrigger id="script-position">241 <SelectValue />242 </SelectTrigger>243 <SelectContent>244 <SelectItem value="head">In <head></SelectItem>245 <SelectItem value="body">Before </body></SelectItem>246 </SelectContent>247 </Select>248 </div>249 250 <div className="space-y-2">251 <Label htmlFor="script-type">Type</Label>252 <Select253 value={editingScript.type}254 onValueChange={(value: 'inline' | 'external') =>255 setEditingScript({ ...editingScript, type: value })256 }257 >258 <SelectTrigger id="script-type">259 <SelectValue />260 </SelectTrigger>261 <SelectContent>262 <SelectItem value="inline">Inline Script</SelectItem>263 <SelectItem value="external">External URL</SelectItem>264 </SelectContent>265 </Select>266 </div>267 </div>268 269 <div className="space-y-2">270 <Label htmlFor="script-content">271 {editingScript.type === 'inline' ? 'Script Code' : 'Script URL'}272 </Label>273 {editingScript.type === 'inline' ? (274 <Textarea275 id="script-content"276 placeholder="<script>...</script>"277 rows={8}278 value={editingScript.content}279 onChange={(e) =>280 setEditingScript({ ...editingScript, content: e.target.value })281 }282 className="font-mono text-sm"283 />284 ) : (285 <Input286 id="script-content"287 type="url"288 placeholder="https://example.com/script.js"289 value={editingScript.content}290 onChange={(e) =>291 setEditingScript({ ...editingScript, content: e.target.value })292 }293 />294 )}295 </div>296 297 {editingScript.type === 'external' && (298 <div className="flex gap-4">299 <div className="flex items-center space-x-2">300 <Switch301 id="script-async"302 checked={editingScript.async || false}303 onCheckedChange={(checked) =>304 setEditingScript({ ...editingScript, async: checked })305 }306 />307 <Label htmlFor="script-async">Async</Label>308 </div>309 <div className="flex items-center space-x-2">310 <Switch311 id="script-defer"312 checked={editingScript.defer || false}313 onCheckedChange={(checked) =>314 setEditingScript({ ...editingScript, defer: checked })315 }316 />317 <Label htmlFor="script-defer">Defer</Label>318 </div>319 </div>320 )}321 </div>322 )}323 324 <DialogFooter>325 <Button variant="outline" onClick={() => setIsDialogOpen(false)}>326 Cancel327 </Button>328 <Button onClick={handleSaveScript}>Save Script</Button>329 </DialogFooter>330 </DialogContent>331 </Dialog>332 </div>333 );334}335 