sonance-brand-mcp 1.3.15 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,621 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Image as ImageIcon, Sun, Moon, CheckCircle, AlertCircle, Loader2, RotateCcw, Save, Check, Wand2 } from "lucide-react";
5
+ import { cn } from "../../../lib/utils";
6
+ import { LogoAsset, LogoOverride, OriginalLogoState, DetectedElement, LogoSaveStatus } from "../types";
7
+ import { Section } from "../components/common";
8
+
9
+ type AutoFixStatus = "idle" | "fixing" | "success" | "error";
10
+
11
+ export interface LogoToolsPanelProps {
12
+ logoAssets: LogoAsset[];
13
+ logoAssetsByBrand: Record<string, LogoAsset[]>;
14
+ selectedLogoId: string | null;
15
+ globalLogoConfig: LogoOverride;
16
+ individualLogoConfigs: Record<string, LogoOverride>;
17
+ originalLogoStates: Record<string, OriginalLogoState>;
18
+ taggedElements: DetectedElement[];
19
+ onGlobalConfigChange: (config: LogoOverride) => void;
20
+ onIndividualConfigChange: (logoId: string, config: LogoOverride) => void;
21
+ onSelectLogo: (logoId: string | null) => void;
22
+ onResetAll: () => void;
23
+ onResetLogo: (logoId: string) => void;
24
+ onSaveChanges: (configOverride?: LogoOverride, brandId?: string, selector?: string, logoId?: string) => void;
25
+ saveStatus: LogoSaveStatus;
26
+ saveMessage: string;
27
+ findComplementaryLogo: (path: string) => { light?: string; dark?: string } | null;
28
+ currentTheme: string;
29
+ onAutoFixId: (logoSrc: string, suggestedId: string) => Promise<{ success: boolean; error?: string }>;
30
+ autoFixStatus: AutoFixStatus;
31
+ autoFixMessage: string;
32
+ }
33
+
34
+ /**
35
+ * Generates a context-aware ID suggestion for a logo element based on its
36
+ * position in the DOM, parent containers, and attributes.
37
+ */
38
+ function generateIdSuggestion(logoId: string, brandId?: string): string {
39
+ // Try to get context from the DOM element
40
+ const element = document.querySelector(`[data-sonance-logo-id="${logoId}"]`);
41
+ if (!element) {
42
+ return brandId ? `${brandId}-logo` : "brand-logo";
43
+ }
44
+
45
+ // Check parent containers for context
46
+ const parentNames: string[] = [];
47
+ let current = element.parentElement;
48
+ let depth = 0;
49
+
50
+ while (current && depth < 5) {
51
+ // Check for semantic elements
52
+ const tagName = current.tagName.toLowerCase();
53
+ if (["header", "footer", "nav", "aside", "main", "section"].includes(tagName)) {
54
+ parentNames.unshift(tagName);
55
+ break;
56
+ }
57
+
58
+ // Check for data-sonance-name attribute
59
+ const sonanceName = current.getAttribute("data-sonance-name");
60
+ if (sonanceName) {
61
+ parentNames.unshift(sonanceName.toLowerCase().replace(/\s+/g, "-"));
62
+ break;
63
+ }
64
+
65
+ // Check for common class hints
66
+ const classList = current.className;
67
+ if (typeof classList === "string") {
68
+ if (classList.includes("header")) parentNames.unshift("header");
69
+ else if (classList.includes("footer")) parentNames.unshift("footer");
70
+ else if (classList.includes("sidebar")) parentNames.unshift("sidebar");
71
+ else if (classList.includes("nav")) parentNames.unshift("nav");
72
+ }
73
+
74
+ current = current.parentElement;
75
+ depth++;
76
+ }
77
+
78
+ // Check alt text for context
79
+ const altText = element.getAttribute("alt");
80
+ if (altText) {
81
+ const cleanAlt = altText.toLowerCase()
82
+ .replace(/[^a-z0-9\s]/g, "")
83
+ .replace(/\s+/g, "-")
84
+ .substring(0, 20);
85
+ if (cleanAlt && cleanAlt !== "logo") {
86
+ parentNames.push(cleanAlt);
87
+ }
88
+ }
89
+
90
+ // Build the suggested ID
91
+ const parts: string[] = [];
92
+ if (parentNames.length > 0) {
93
+ parts.push(parentNames[0]);
94
+ }
95
+ if (brandId) {
96
+ parts.push(brandId);
97
+ }
98
+ parts.push("logo");
99
+
100
+ return parts.join("-");
101
+ }
102
+
103
+ export function LogoToolsPanel({
104
+ logoAssets,
105
+ logoAssetsByBrand,
106
+ selectedLogoId,
107
+ globalLogoConfig,
108
+ individualLogoConfigs,
109
+ originalLogoStates,
110
+ taggedElements,
111
+ onGlobalConfigChange,
112
+ onIndividualConfigChange,
113
+ onSelectLogo,
114
+ onResetAll,
115
+ onResetLogo,
116
+ onSaveChanges,
117
+ saveStatus,
118
+ saveMessage,
119
+ findComplementaryLogo,
120
+ currentTheme,
121
+ onAutoFixId,
122
+ autoFixStatus,
123
+ autoFixMessage,
124
+ }: LogoToolsPanelProps) {
125
+ const logoElements = taggedElements.filter((el) => el.type === "logo" && el.logoId);
126
+ const selectedLogo = logoElements.find((el) => el.logoId === selectedLogoId);
127
+ const selectedConfig = selectedLogoId ? individualLogoConfigs[selectedLogoId] || {} : {};
128
+ const selectedOriginal = selectedLogoId ? originalLogoStates[selectedLogoId] : null;
129
+
130
+ // Attempt to identify the brand and element ID of the selected logo
131
+ let selectedBrandId: string | undefined;
132
+ let selectedElementId: string | undefined;
133
+
134
+ if (selectedLogoId) {
135
+ // Check if the element has an ID attribute for targeted CSS
136
+ const logoElement = document.querySelector(`[data-sonance-logo-id="${selectedLogoId}"]`);
137
+ if (logoElement && logoElement.id) {
138
+ selectedElementId = logoElement.id;
139
+ }
140
+ }
141
+
142
+ if (selectedOriginal && selectedOriginal.src) {
143
+ // Try to find matching asset
144
+ // Normalize paths for comparison (remove protocol/domain if present)
145
+ const normalize = (p: string) => p.split("?")[0].split("#")[0]; // remove query/hash
146
+ const originalPath = normalize(selectedOriginal.src);
147
+
148
+ // Find asset where path ends with the original src filename or vice versa
149
+ const asset = logoAssets.find(a =>
150
+ originalPath.endsWith(a.path) || a.path.endsWith(originalPath) || originalPath.includes(a.name)
151
+ );
152
+ if (asset) {
153
+ selectedBrandId = asset.brand;
154
+ } else {
155
+ // Fallback: guess from path
156
+ if (originalPath.toLowerCase().includes("sonance")) selectedBrandId = "sonance";
157
+ else if (originalPath.toLowerCase().includes("iport")) selectedBrandId = "iport";
158
+ else if (originalPath.toLowerCase().includes("blaze")) selectedBrandId = "blaze";
159
+ }
160
+ }
161
+
162
+ // Get sorted brand keys
163
+ const brandKeys = Object.keys(logoAssetsByBrand).sort();
164
+
165
+ return (
166
+ <div className="space-y-5">
167
+ {/* Header info */}
168
+ <div className="p-3 rounded border border-orange-200 bg-orange-50">
169
+ <p id="analysis-modal-p" className="text-xs text-orange-700">
170
+ <strong>{logoElements.length}</strong> logo{logoElements.length !== 1 ? "s" : ""} detected on this page.
171
+ Click a logo on the page to select it for editing.
172
+ </p>
173
+ </div>
174
+
175
+ {/* Global Controls */}
176
+ <Section title="Replace All Logos">
177
+ <div className="space-y-3">
178
+ {/* Theme indicator */}
179
+ <div className="flex items-center gap-2 text-[10px] text-gray-400">
180
+ <span id="section-span-currenttheme-dark-da" className={cn("px-1.5 py-0.5 rounded", currentTheme === "light" ? "bg-yellow-100 text-yellow-700" : "bg-gray-700 text-gray-200")}>
181
+ {currentTheme === "dark" ? "🌙 Dark Mode" : "☀️ Light Mode"}
182
+ </span>
183
+ <span id="section-span-current-preview">Current preview</span>
184
+ </div>
185
+
186
+ {/* Light Mode Logo */}
187
+ <div className="space-y-1.5">
188
+ <label className="text-xs text-gray-500 flex items-center gap-1.5">
189
+ <Sun className="h-3 w-3 text-yellow-500" />
190
+ Light Mode Logo:
191
+ </label>
192
+ <select
193
+ value={globalLogoConfig.srcLight || ""}
194
+ onChange={(e) => {
195
+ const newPath = e.target.value || undefined;
196
+ if (newPath) {
197
+ // Auto-fill dark variant if available
198
+ const complementary = findComplementaryLogo(newPath);
199
+ onGlobalConfigChange({
200
+ ...globalLogoConfig,
201
+ srcLight: newPath,
202
+ srcDark: complementary?.dark || globalLogoConfig.srcDark,
203
+ });
204
+ } else {
205
+ onGlobalConfigChange({ ...globalLogoConfig, srcLight: undefined });
206
+ }
207
+ }}
208
+ className={cn(
209
+ "w-full h-8 px-2 text-xs rounded",
210
+ "border border-gray-200 bg-white text-gray-700",
211
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
212
+ )}
213
+ >
214
+ <option value="">-- Select light mode logo --</option>
215
+ {brandKeys.map((brand) => (
216
+ <optgroup key={brand} label={brand.charAt(0).toUpperCase() + brand.slice(1)}>
217
+ {logoAssetsByBrand[brand].map((asset) => (
218
+ <option key={asset.id} value={asset.path}>
219
+ {asset.name}
220
+ </option>
221
+ ))}
222
+ </optgroup>
223
+ ))}
224
+ </select>
225
+ </div>
226
+
227
+ {/* Dark Mode Logo */}
228
+ <div className="space-y-1.5">
229
+ <label className="text-xs text-gray-500 flex items-center gap-1.5">
230
+ <Moon className="h-3 w-3 text-blue-400" />
231
+ Dark Mode Logo:
232
+ </label>
233
+ <select
234
+ value={globalLogoConfig.srcDark || ""}
235
+ onChange={(e) => {
236
+ const newPath = e.target.value || undefined;
237
+ if (newPath) {
238
+ // Auto-fill light variant if available
239
+ const complementary = findComplementaryLogo(newPath);
240
+ onGlobalConfigChange({
241
+ ...globalLogoConfig,
242
+ srcDark: newPath,
243
+ srcLight: complementary?.light || globalLogoConfig.srcLight,
244
+ });
245
+ } else {
246
+ onGlobalConfigChange({ ...globalLogoConfig, srcDark: undefined });
247
+ }
248
+ }}
249
+ className={cn(
250
+ "w-full h-8 px-2 text-xs rounded",
251
+ "border border-gray-200 bg-white text-gray-700",
252
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
253
+ )}
254
+ >
255
+ <option value="">-- Select dark mode logo --</option>
256
+ {brandKeys.map((brand) => (
257
+ <optgroup key={brand} label={brand.charAt(0).toUpperCase() + brand.slice(1)}>
258
+ {logoAssetsByBrand[brand].map((asset) => (
259
+ <option key={asset.id} value={asset.path}>
260
+ {asset.name}
261
+ </option>
262
+ ))}
263
+ </optgroup>
264
+ ))}
265
+ </select>
266
+ </div>
267
+
268
+ <div className="space-y-1.5">
269
+ <label className="text-xs text-gray-500">Scale: {Math.round((globalLogoConfig.scale || 1) * 100)}%</label>
270
+ <input
271
+ type="range"
272
+ min="25"
273
+ max="200"
274
+ value={(globalLogoConfig.scale || 1) * 100}
275
+ onChange={(e) => onGlobalConfigChange({ ...globalLogoConfig, scale: parseInt(e.target.value) / 100 })}
276
+ className="w-full h-2 rounded-lg appearance-none cursor-pointer bg-gray-200"
277
+ />
278
+ </div>
279
+ </div>
280
+ </Section>
281
+
282
+ {/* Save Status Message */}
283
+ {saveMessage && (
284
+ <div
285
+ className={cn(
286
+ "flex items-start gap-2 p-2 rounded text-xs",
287
+ saveStatus === "success" && "bg-green-50 text-green-700 border border-green-200",
288
+ saveStatus === "error" && "bg-red-50 text-red-700 border border-red-200"
289
+ )}
290
+ >
291
+ {saveStatus === "success" && <CheckCircle className="h-3.5 w-3.5 shrink-0 mt-0.5" />}
292
+ {saveStatus === "error" && <AlertCircle className="h-3.5 w-3.5 shrink-0 mt-0.5" />}
293
+ <span id="analysis-modal-span-savemessage">{saveMessage}</span>
294
+ </div>
295
+ )}
296
+
297
+ {/* Save Button */}
298
+ {(globalLogoConfig.reset || globalLogoConfig.srcLight || globalLogoConfig.srcDark) && (
299
+ <button
300
+ onClick={() => onSaveChanges()}
301
+ disabled={saveStatus === "saving"}
302
+ className={cn(
303
+ "w-full flex items-center justify-center gap-2 py-2.5",
304
+ "text-sm font-medium text-white rounded transition-colors",
305
+ globalLogoConfig.reset ? "bg-amber-600 hover:bg-amber-700" : "bg-[#333F48] hover:bg-[#2a343c]",
306
+ "disabled:opacity-50 disabled:cursor-not-allowed"
307
+ )}
308
+ >
309
+ {saveStatus === "saving" ? (
310
+ <>
311
+ <Loader2 className="h-4 w-4 animate-spin" />
312
+ Saving...
313
+ </>
314
+ ) : (
315
+ <>
316
+ {globalLogoConfig.reset ? <RotateCcw className="h-4 w-4" /> : <Save className="h-4 w-4" />}
317
+ {globalLogoConfig.reset ? "Save Reset" : "Save to Brand System"}
318
+ </>
319
+ )}
320
+ </button>
321
+ )}
322
+
323
+ {/* Selected Logo Controls */}
324
+ {selectedLogoId && selectedLogo && (
325
+ <Section title={`Edit: ${selectedLogo.name}`}>
326
+ <div className="space-y-3 p-3 rounded border border-[#FC4C02] bg-orange-50/50">
327
+ {/* Original info */}
328
+ {selectedOriginal && (
329
+ <div className="text-[10px] text-gray-500">
330
+ Original: {selectedOriginal.width} × {selectedOriginal.height}px
331
+ </div>
332
+ )}
333
+
334
+ {/* Theme indicator */}
335
+ <div className="flex items-center gap-2 text-[10px] text-gray-400">
336
+ <span id="analysis-modal-span-currenttheme-dark-da" className={cn("px-1.5 py-0.5 rounded", currentTheme === "light" ? "bg-yellow-100 text-yellow-700" : "bg-gray-700 text-gray-200")}>
337
+ {currentTheme === "dark" ? "🌙 Dark Mode" : "☀️ Light Mode"}
338
+ </span>
339
+ <span id="analysis-modal-span-current-preview">Current preview</span>
340
+ </div>
341
+
342
+ {/* Light Mode Logo */}
343
+ <div className="space-y-1.5">
344
+ <label className="text-xs text-gray-500 flex items-center gap-1.5">
345
+ <Sun className="h-3 w-3 text-yellow-500" />
346
+ Light Mode Logo:
347
+ </label>
348
+ <select
349
+ value={selectedConfig.srcLight || ""}
350
+ onChange={(e) => {
351
+ const newPath = e.target.value || undefined;
352
+ if (newPath) {
353
+ const complementary = findComplementaryLogo(newPath);
354
+ onIndividualConfigChange(selectedLogoId, {
355
+ ...selectedConfig,
356
+ srcLight: newPath,
357
+ srcDark: complementary?.dark || selectedConfig.srcDark,
358
+ });
359
+ } else {
360
+ onIndividualConfigChange(selectedLogoId, { ...selectedConfig, srcLight: undefined });
361
+ }
362
+ }}
363
+ className={cn(
364
+ "w-full h-8 px-2 text-xs rounded",
365
+ "border border-gray-200 bg-white text-gray-700",
366
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
367
+ )}
368
+ >
369
+ <option value="">-- Use global/original --</option>
370
+ {brandKeys.map((brand) => (
371
+ <optgroup key={brand} label={brand.charAt(0).toUpperCase() + brand.slice(1)}>
372
+ {logoAssetsByBrand[brand].map((asset) => (
373
+ <option key={asset.id} value={asset.path}>
374
+ {asset.name}
375
+ </option>
376
+ ))}
377
+ </optgroup>
378
+ ))}
379
+ </select>
380
+ </div>
381
+
382
+ {/* Dark Mode Logo */}
383
+ <div className="space-y-1.5">
384
+ <label className="text-xs text-gray-500 flex items-center gap-1.5">
385
+ <Moon className="h-3 w-3 text-blue-400" />
386
+ Dark Mode Logo:
387
+ </label>
388
+ <select
389
+ value={selectedConfig.srcDark || ""}
390
+ onChange={(e) => {
391
+ const newPath = e.target.value || undefined;
392
+ if (newPath) {
393
+ const complementary = findComplementaryLogo(newPath);
394
+ onIndividualConfigChange(selectedLogoId, {
395
+ ...selectedConfig,
396
+ srcDark: newPath,
397
+ srcLight: complementary?.light || selectedConfig.srcLight,
398
+ });
399
+ } else {
400
+ onIndividualConfigChange(selectedLogoId, { ...selectedConfig, srcDark: undefined });
401
+ }
402
+ }}
403
+ className={cn(
404
+ "w-full h-8 px-2 text-xs rounded",
405
+ "border border-gray-200 bg-white text-gray-700",
406
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
407
+ )}
408
+ >
409
+ <option value="">-- Use global/original --</option>
410
+ {brandKeys.map((brand) => (
411
+ <optgroup key={brand} label={brand.charAt(0).toUpperCase() + brand.slice(1)}>
412
+ {logoAssetsByBrand[brand].map((asset) => (
413
+ <option key={asset.id} value={asset.path}>
414
+ {asset.name}
415
+ </option>
416
+ ))}
417
+ </optgroup>
418
+ ))}
419
+ </select>
420
+ </div>
421
+
422
+ {/* Dimensions */}
423
+ <div className="grid grid-cols-2 gap-2">
424
+ <div className="space-y-1">
425
+ <label className="text-xs text-gray-500">Width (px)</label>
426
+ <input
427
+ type="number"
428
+ min="10"
429
+ max="1000"
430
+ value={selectedConfig.width || ""}
431
+ placeholder="auto"
432
+ onChange={(e) => onIndividualConfigChange(selectedLogoId, {
433
+ ...selectedConfig,
434
+ width: e.target.value ? parseInt(e.target.value) : undefined
435
+ })}
436
+ className={cn(
437
+ "w-full h-8 px-2 text-xs rounded",
438
+ "border border-gray-200 bg-white text-gray-700",
439
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
440
+ )}
441
+ />
442
+ </div>
443
+ <div className="space-y-1">
444
+ <label className="text-xs text-gray-500">Height (px)</label>
445
+ <input
446
+ type="number"
447
+ min="10"
448
+ max="1000"
449
+ value={selectedConfig.height || ""}
450
+ placeholder="auto"
451
+ onChange={(e) => onIndividualConfigChange(selectedLogoId, {
452
+ ...selectedConfig,
453
+ height: e.target.value ? parseInt(e.target.value) : undefined
454
+ })}
455
+ className={cn(
456
+ "w-full h-8 px-2 text-xs rounded",
457
+ "border border-gray-200 bg-white text-gray-700",
458
+ "focus:outline-none focus:ring-1 focus:ring-[#FC4C02]"
459
+ )}
460
+ />
461
+ </div>
462
+ </div>
463
+
464
+ {/* Scale */}
465
+ <div className="space-y-1.5">
466
+ <label className="text-xs text-gray-500">Scale: {Math.round((selectedConfig.scale || 1) * 100)}%</label>
467
+ <input
468
+ type="range"
469
+ min="25"
470
+ max="200"
471
+ value={(selectedConfig.scale || 1) * 100}
472
+ onChange={(e) => onIndividualConfigChange(selectedLogoId, { ...selectedConfig, scale: parseInt(e.target.value) / 100 })}
473
+ className="w-full h-2 rounded-lg appearance-none cursor-pointer bg-gray-200"
474
+ />
475
+ </div>
476
+
477
+ {/* Warning if element has no ID for individual persistence */}
478
+ {!selectedElementId && (selectedConfig.width || selectedConfig.height || selectedConfig.scale) && (() => {
479
+ const suggestedId = generateIdSuggestion(selectedLogoId, selectedBrandId);
480
+ const codeSnippet = `id="${suggestedId}"`;
481
+ const logoSrc = selectedOriginal?.src || "";
482
+
483
+ return (
484
+ <div className="p-2.5 rounded border border-amber-200 bg-amber-50 space-y-2">
485
+ <p id="analysis-modal-p" className="text-[10px] text-amber-700">
486
+ <strong>⚠️ No ID found.</strong> Saving will update the <strong>global {selectedBrandId || "brand"} default</strong>, affecting all logos of this brand.
487
+ </p>
488
+ <div className="flex items-center gap-2">
489
+ <code className="flex-1 px-2 py-1 text-[10px] bg-amber-100 text-amber-800 rounded font-mono truncate" title={codeSnippet}>
490
+ {codeSnippet}
491
+ </code>
492
+ <button
493
+ onClick={() => onAutoFixId(logoSrc, suggestedId)}
494
+ disabled={autoFixStatus === "fixing"}
495
+ className={cn(
496
+ "flex items-center gap-1 px-2 py-1 text-[10px] font-medium rounded transition-colors whitespace-nowrap",
497
+ autoFixStatus === "success"
498
+ ? "bg-green-100 text-green-700"
499
+ : autoFixStatus === "error"
500
+ ? "bg-red-100 text-red-700"
501
+ : "text-amber-700 bg-amber-100 hover:bg-amber-200",
502
+ autoFixStatus === "fixing" && "opacity-50 cursor-not-allowed"
503
+ )}
504
+ title="Automatically inject this ID into your source code"
505
+ >
506
+ {autoFixStatus === "fixing" ? (
507
+ <>
508
+ <Loader2 className="h-3 w-3 animate-spin" />
509
+ Fixing...
510
+ </>
511
+ ) : autoFixStatus === "success" ? (
512
+ <>
513
+ <Check className="h-3 w-3" />
514
+ Fixed!
515
+ </>
516
+ ) : autoFixStatus === "error" ? (
517
+ <>
518
+ <AlertCircle className="h-3 w-3" />
519
+ Failed
520
+ </>
521
+ ) : (
522
+ <>
523
+ <Wand2 className="h-3 w-3" />
524
+ Auto-Fix
525
+ </>
526
+ )}
527
+ </button>
528
+ </div>
529
+ {autoFixMessage && (
530
+ <p id="analysis-modal-p-autofixmessage" className={cn(
531
+ "text-[9px]",
532
+ autoFixStatus === "success" ? "text-green-600" : autoFixStatus === "error" ? "text-red-600" : "text-amber-600"
533
+ )}>
534
+ {autoFixMessage}
535
+ </p>
536
+ )}
537
+ {!autoFixMessage && (
538
+ <p id="analysis-modal-p-click-autofix-to-inj" className="text-[9px] text-amber-600">
539
+ Click Auto-Fix to inject this ID into your source code automatically.
540
+ </p>
541
+ )}
542
+ </div>
543
+ );
544
+ })()}
545
+
546
+ {/* Reset this logo */}
547
+ <div className="flex gap-2">
548
+ <button
549
+ onClick={() => {
550
+ if (selectedLogoId) {
551
+ onResetLogo(selectedLogoId);
552
+ }
553
+ }}
554
+ className={cn(
555
+ "flex-1 flex items-center justify-center gap-2 py-2",
556
+ "text-xs font-medium text-gray-500 hover:text-gray-700",
557
+ "border border-gray-200 rounded hover:bg-white transition-colors"
558
+ )}
559
+ >
560
+ <RotateCcw className="h-3 w-3" />
561
+ Reset
562
+ </button>
563
+
564
+ {(selectedConfig.reset || selectedConfig.srcLight || selectedConfig.srcDark || selectedConfig.src || selectedConfig.width || selectedConfig.height || (selectedConfig.scale && selectedConfig.scale !== 1)) && (
565
+ <button
566
+ onClick={() => onSaveChanges(selectedConfig, selectedBrandId, selectedElementId ? `#${selectedElementId}` : undefined, selectedLogoId || undefined)}
567
+ disabled={saveStatus === "saving"}
568
+ className={cn(
569
+ "flex-1 flex items-center justify-center gap-2 py-2",
570
+ "text-xs font-medium text-white rounded transition-colors",
571
+ selectedConfig.reset ? "bg-amber-600 hover:bg-amber-700" : (selectedElementId ? "bg-[#00A3E1] hover:bg-[#0090c8]" : "bg-[#333F48] hover:bg-[#2a343c]"),
572
+ "disabled:opacity-50 disabled:cursor-not-allowed"
573
+ )}
574
+ title={selectedConfig.reset ? "Save Reset (Delete Override)" : (selectedElementId ? `Save to element #${selectedElementId}` : (selectedBrandId ? `Save as ${selectedBrandId} brand default` : "Save as brand default"))}
575
+ >
576
+ {saveStatus === "saving" ? (
577
+ <Loader2 className="h-3 w-3 animate-spin" />
578
+ ) : (
579
+ <>
580
+ {selectedConfig.reset ? <RotateCcw className="h-3 w-3" /> : <Save className="h-3 w-3" />}
581
+ {selectedConfig.reset ? "Save Reset" : (selectedElementId ? "Save to Element" : "Save to Brand")}
582
+ </>
583
+ )}
584
+ </button>
585
+ )}
586
+ </div>
587
+
588
+ {/* Individual Save Status Message */}
589
+ {saveMessage && (saveStatus === "success" || saveStatus === "error") && (
590
+ <div
591
+ className={cn(
592
+ "flex items-center gap-2 p-2 rounded text-xs mt-2",
593
+ saveStatus === "success" && "bg-green-50 text-green-700 border border-green-200",
594
+ saveStatus === "error" && "bg-red-50 text-red-700 border border-red-200"
595
+ )}
596
+ >
597
+ {saveStatus === "success" && <CheckCircle className="h-3.5 w-3.5 shrink-0" />}
598
+ {saveStatus === "error" && <AlertCircle className="h-3.5 w-3.5 shrink-0" />}
599
+ <span id="text-panel-span-savemessage">{saveMessage}</span>
600
+ </div>
601
+ )}
602
+ </div>
603
+ </Section>
604
+ )}
605
+
606
+ {/* Reset All */}
607
+ <button
608
+ onClick={onResetAll}
609
+ className={cn(
610
+ "w-full flex items-center justify-center gap-2 py-2.5",
611
+ "text-xs font-medium text-gray-500 hover:text-gray-700",
612
+ "border border-gray-200 rounded hover:bg-gray-50 transition-colors"
613
+ )}
614
+ >
615
+ <RotateCcw className="h-3.5 w-3.5" />
616
+ Reset All Logos
617
+ </button>
618
+ </div>
619
+ );
620
+ }
621
+
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { LogoToolsPanel, LogoToolsPanelProps } from "./LogoToolsPanel";
5
+
6
+ // LogosPanel now just wraps LogoToolsPanel - inspector is controlled via header
7
+ export type LogosPanelProps = LogoToolsPanelProps;
8
+
9
+ export function LogosPanel(props: LogosPanelProps) {
10
+ return (
11
+ <div className="space-y-4">
12
+ <LogoToolsPanel {...props} />
13
+ </div>
14
+ );
15
+ }
16
+