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.
- package/dist/assets/api/sonance-analyze/route.ts +1 -1
- package/dist/assets/api/sonance-save-logo/route.ts +2 -2
- package/dist/assets/brand-system.ts +4 -1
- package/dist/assets/components/image.tsx +3 -1
- package/dist/assets/components/select.tsx +3 -0
- package/dist/assets/dev-tools/SonanceDevTools.tsx +1837 -3579
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +230 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +455 -0
- package/dist/assets/dev-tools/components/DiffPreview.tsx +190 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +353 -0
- package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +199 -0
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +116 -0
- package/dist/assets/dev-tools/components/common.tsx +94 -0
- package/dist/assets/dev-tools/constants.ts +616 -0
- package/dist/assets/dev-tools/index.ts +29 -8
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +329 -0
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +623 -0
- package/dist/assets/dev-tools/panels/LogoToolsPanel.tsx +621 -0
- package/dist/assets/dev-tools/panels/LogosPanel.tsx +16 -0
- package/dist/assets/dev-tools/panels/TextPanel.tsx +332 -0
- package/dist/assets/dev-tools/types.ts +295 -0
- package/dist/assets/dev-tools/utils.ts +360 -0
- package/dist/index.js +268 -0
- package/package.json +1 -1
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// Usage:
|
|
4
|
-
// import { SonanceDevTools } from '@/components/dev-tools';
|
|
5
|
-
//
|
|
6
|
-
// In your root layout:
|
|
7
|
-
// {process.env.NODE_ENV === 'development' && <SonanceDevTools />}
|
|
1
|
+
// Main component
|
|
2
|
+
export { SonanceDevTools } from "./SonanceDevTools";
|
|
8
3
|
|
|
9
|
-
|
|
4
|
+
// Types
|
|
5
|
+
export * from "./types";
|
|
10
6
|
|
|
7
|
+
// Constants
|
|
8
|
+
export * from "./constants";
|
|
9
|
+
|
|
10
|
+
// Utilities (explicit exports to avoid conflict with constants.ts getVisibleSections)
|
|
11
|
+
export {
|
|
12
|
+
generateComponentCSS,
|
|
13
|
+
injectPreviewStyles,
|
|
14
|
+
removePreviewStyles,
|
|
15
|
+
tailwindClassToCSS,
|
|
16
|
+
extractPreviewCSSFromCode,
|
|
17
|
+
shouldShowScopeOptions,
|
|
18
|
+
} from "./utils";
|
|
19
|
+
|
|
20
|
+
// Shared Components
|
|
21
|
+
export { Section, ColorSwatch, SelectField } from "./components/common";
|
|
22
|
+
export { InspectorOverlay } from "./components/InspectorOverlay";
|
|
23
|
+
export { ChatInterface } from "./components/ChatInterface";
|
|
24
|
+
export { DiffPreview } from "./components/DiffPreview";
|
|
25
|
+
|
|
26
|
+
// Panels
|
|
27
|
+
export { AnalysisPanel } from "./panels/AnalysisPanel";
|
|
28
|
+
export { TextPanel } from "./panels/TextPanel";
|
|
29
|
+
export { LogoToolsPanel } from "./panels/LogoToolsPanel";
|
|
30
|
+
export { LogosPanel } from "./panels/LogosPanel";
|
|
31
|
+
export { ComponentsPanel } from "./panels/ComponentsPanel";
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Scan, Loader2, AlertCircle, RotateCcw, Image as ImageIcon, Type, MousePointer, Box, FormInput, CheckCircle, Wand2 } from "lucide-react";
|
|
5
|
+
import { cn } from "../../../lib/utils";
|
|
6
|
+
import { AnalysisResult, AnalysisStatus } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface AnalysisPanelProps {
|
|
9
|
+
analysisStatus: AnalysisStatus;
|
|
10
|
+
analysisResult: AnalysisResult | null;
|
|
11
|
+
analysisError: string;
|
|
12
|
+
bulkTagStatus: "idle" | "tagging" | "complete";
|
|
13
|
+
bulkTagMessage: string;
|
|
14
|
+
onRunAnalysis: () => void;
|
|
15
|
+
onBulkTag: (elementIds?: string[], category?: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AnalysisPanel({
|
|
19
|
+
analysisStatus,
|
|
20
|
+
analysisResult,
|
|
21
|
+
analysisError,
|
|
22
|
+
bulkTagStatus,
|
|
23
|
+
bulkTagMessage,
|
|
24
|
+
onRunAnalysis,
|
|
25
|
+
onBulkTag,
|
|
26
|
+
}: AnalysisPanelProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="space-y-4">
|
|
29
|
+
{/* Initial State */}
|
|
30
|
+
{analysisStatus === "idle" && !analysisResult && (
|
|
31
|
+
<div className="text-center py-6 space-y-4">
|
|
32
|
+
<div className="mx-auto w-14 h-14 rounded-full bg-gray-100 flex items-center justify-center">
|
|
33
|
+
<Scan className="h-7 w-7 text-gray-400" />
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<h3 id="analysis-panel-h3-analyze-your-project" className="text-base font-medium">Analyze Your Project</h3>
|
|
37
|
+
<p id="analysis-panel-p-scan-your-codebase-t" className="text-xs text-gray-500 mt-1">
|
|
38
|
+
Scan your codebase to index all images, logos, and theme files.
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
<button
|
|
42
|
+
onClick={onRunAnalysis}
|
|
43
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-[#333F48] text-white text-sm font-medium rounded hover:bg-[#2a343c] transition-colors"
|
|
44
|
+
>
|
|
45
|
+
<Scan className="h-4 w-4" />
|
|
46
|
+
Scan Project
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{/* Scanning State */}
|
|
52
|
+
{analysisStatus === "scanning" && (
|
|
53
|
+
<div className="text-center py-6 space-y-4">
|
|
54
|
+
<div className="mx-auto w-14 h-14 rounded-full bg-blue-50 flex items-center justify-center">
|
|
55
|
+
<Loader2 className="h-7 w-7 text-[#00A3E1] animate-spin" />
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<h3 id="analysis-panel-h3-scanning" className="text-base font-medium">Scanning...</h3>
|
|
59
|
+
<p id="analysis-panel-p-analyzing-your-sourc" className="text-xs text-gray-500 mt-1">
|
|
60
|
+
Analyzing your source files for design assets.
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
|
|
66
|
+
{/* Error State */}
|
|
67
|
+
{analysisStatus === "error" && (
|
|
68
|
+
<div className="text-center py-6 space-y-4">
|
|
69
|
+
<div className="mx-auto w-14 h-14 rounded-full bg-red-50 flex items-center justify-center">
|
|
70
|
+
<AlertCircle className="h-7 w-7 text-red-500" />
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<h3 id="analysis-panel-h3-analysis-failed" className="text-base font-medium text-red-600">Analysis Failed</h3>
|
|
74
|
+
<p id="analysis-panel-p-analysiserror" className="text-xs text-gray-500 mt-1">{analysisError}</p>
|
|
75
|
+
</div>
|
|
76
|
+
<button
|
|
77
|
+
onClick={onRunAnalysis}
|
|
78
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-[#333F48] text-white text-sm font-medium rounded hover:bg-[#2a343c] transition-colors"
|
|
79
|
+
>
|
|
80
|
+
<RotateCcw className="h-4 w-4" />
|
|
81
|
+
Try Again
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{/* Results */}
|
|
87
|
+
{analysisStatus === "complete" && analysisResult && (
|
|
88
|
+
<>
|
|
89
|
+
{/* Overview Stats */}
|
|
90
|
+
<div className="grid grid-cols-3 gap-2">
|
|
91
|
+
<div className="p-2 rounded border border-gray-200 bg-gray-50 text-center">
|
|
92
|
+
<div className="text-[10px] text-gray-500 uppercase tracking-wide">Files</div>
|
|
93
|
+
<div className="text-lg font-semibold">{analysisResult.filesScanned}</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="p-2 rounded border border-green-200 bg-green-50 text-center">
|
|
96
|
+
<div className="text-[10px] text-green-600 uppercase tracking-wide">With IDs</div>
|
|
97
|
+
<div className="text-lg font-semibold text-green-700">{analysisResult.summary.elementsWithId}</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div className="p-2 rounded border border-amber-200 bg-amber-50 text-center">
|
|
100
|
+
<div className="text-[10px] text-amber-600 uppercase tracking-wide">Missing</div>
|
|
101
|
+
<div className="text-lg font-semibold text-amber-700">{analysisResult.summary.elementsMissingId}</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Category Breakdown */}
|
|
106
|
+
<div className="space-y-2">
|
|
107
|
+
<h4 id="analysis-panel-h4-elements-by-category" className="text-xs font-medium text-gray-500 uppercase tracking-wide">Elements by Category</h4>
|
|
108
|
+
<div className="space-y-1.5">
|
|
109
|
+
{/* Images */}
|
|
110
|
+
{(() => {
|
|
111
|
+
const cat = analysisResult.summary.byCategory?.image;
|
|
112
|
+
const total = cat?.total || analysisResult.summary.totalImages;
|
|
113
|
+
const withId = cat?.withId || analysisResult.summary.imagesWithId;
|
|
114
|
+
const missingId = cat?.missingId || analysisResult.summary.imagesMissingId;
|
|
115
|
+
const isComplete = withId === total && total > 0;
|
|
116
|
+
return (
|
|
117
|
+
<div className={cn(
|
|
118
|
+
"flex items-center justify-between p-2 rounded border",
|
|
119
|
+
isComplete ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"
|
|
120
|
+
)}>
|
|
121
|
+
<div className="flex items-center gap-2">
|
|
122
|
+
<ImageIcon className={cn("h-3.5 w-3.5", isComplete ? "text-green-600" : "text-blue-600")} />
|
|
123
|
+
<span id="analysis-panel-span-images" className="text-xs font-medium">Images</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div className="flex items-center gap-2 text-xs">
|
|
126
|
+
<span id="analysis-panel-span-withidtotal" className={isComplete ? "text-green-700" : "text-gray-600"}>{withId}/{total}</span>
|
|
127
|
+
{missingId > 0 && (
|
|
128
|
+
<button
|
|
129
|
+
onClick={() => onBulkTag(undefined, "image")}
|
|
130
|
+
disabled={bulkTagStatus === "tagging"}
|
|
131
|
+
className="px-1.5 py-0.5 text-[10px] font-medium text-white bg-blue-500 hover:bg-blue-600 rounded disabled:opacity-50"
|
|
132
|
+
>
|
|
133
|
+
Tag {missingId}
|
|
134
|
+
</button>
|
|
135
|
+
)}
|
|
136
|
+
{isComplete && <CheckCircle className="h-3 w-3 text-green-600" />}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
})()}
|
|
141
|
+
|
|
142
|
+
{/* Text Elements */}
|
|
143
|
+
{analysisResult.summary.byCategory?.text && (() => {
|
|
144
|
+
const cat = analysisResult.summary.byCategory.text;
|
|
145
|
+
const isComplete = cat.withId === cat.total && cat.total > 0;
|
|
146
|
+
return (
|
|
147
|
+
<div className={cn(
|
|
148
|
+
"flex items-center justify-between p-2 rounded border",
|
|
149
|
+
isComplete ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"
|
|
150
|
+
)}>
|
|
151
|
+
<div className="flex items-center gap-2">
|
|
152
|
+
<Type className={cn("h-3.5 w-3.5", isComplete ? "text-green-600" : "text-purple-600")} />
|
|
153
|
+
<span id="analysis-panel-span-text" className="text-xs font-medium">Text</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div className="flex items-center gap-2 text-xs">
|
|
156
|
+
<span id="analysis-panel-span-catwithidcattotal" className={isComplete ? "text-green-700" : "text-gray-600"}>{cat.withId}/{cat.total}</span>
|
|
157
|
+
{cat.missingId > 0 && (
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => onBulkTag(undefined, "text")}
|
|
160
|
+
disabled={bulkTagStatus === "tagging"}
|
|
161
|
+
className="px-1.5 py-0.5 text-[10px] font-medium text-white bg-purple-500 hover:bg-purple-600 rounded disabled:opacity-50"
|
|
162
|
+
>
|
|
163
|
+
Tag {cat.missingId}
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
{isComplete && <CheckCircle className="h-3 w-3 text-green-600" />}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
})()}
|
|
171
|
+
|
|
172
|
+
{/* Interactive (Buttons, Links) */}
|
|
173
|
+
{analysisResult.summary.byCategory?.interactive && (() => {
|
|
174
|
+
const cat = analysisResult.summary.byCategory.interactive;
|
|
175
|
+
const isComplete = cat.withId === cat.total && cat.total > 0;
|
|
176
|
+
return (
|
|
177
|
+
<div className={cn(
|
|
178
|
+
"flex items-center justify-between p-2 rounded border",
|
|
179
|
+
isComplete ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"
|
|
180
|
+
)}>
|
|
181
|
+
<div className="flex items-center gap-2">
|
|
182
|
+
<MousePointer className={cn("h-3.5 w-3.5", isComplete ? "text-green-600" : "text-cyan-600")} />
|
|
183
|
+
<span id="analysis-panel-span-interactive" className="text-xs font-medium">Interactive</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div className="flex items-center gap-2 text-xs">
|
|
186
|
+
<span id="analysis-panel-span-catwithidcattotal" className={isComplete ? "text-green-700" : "text-gray-600"}>{cat.withId}/{cat.total}</span>
|
|
187
|
+
{cat.missingId > 0 && (
|
|
188
|
+
<button
|
|
189
|
+
onClick={() => onBulkTag(undefined, "interactive")}
|
|
190
|
+
disabled={bulkTagStatus === "tagging"}
|
|
191
|
+
className="px-1.5 py-0.5 text-[10px] font-medium text-white bg-cyan-500 hover:bg-cyan-600 rounded disabled:opacity-50"
|
|
192
|
+
>
|
|
193
|
+
Tag {cat.missingId}
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
{isComplete && <CheckCircle className="h-3 w-3 text-green-600" />}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
})()}
|
|
201
|
+
|
|
202
|
+
{/* Component Definitions */}
|
|
203
|
+
{analysisResult.summary.byCategory?.definition && (() => {
|
|
204
|
+
const cat = analysisResult.summary.byCategory.definition;
|
|
205
|
+
const isComplete = cat.withId === cat.total && cat.total > 0;
|
|
206
|
+
return (
|
|
207
|
+
<div className={cn(
|
|
208
|
+
"flex items-center justify-between p-2 rounded border",
|
|
209
|
+
isComplete ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"
|
|
210
|
+
)}>
|
|
211
|
+
<div className="flex items-center gap-2">
|
|
212
|
+
<Box className={cn("h-3.5 w-3.5", isComplete ? "text-green-600" : "text-violet-600")} />
|
|
213
|
+
<span id="analysis-panel-span-definitions" className="text-xs font-medium">Components</span>
|
|
214
|
+
</div>
|
|
215
|
+
<div className="flex items-center gap-2 text-xs">
|
|
216
|
+
<span id="analysis-panel-span-defwithidtotal" className={isComplete ? "text-green-700" : "text-gray-600"}>{cat.withId}/{cat.total}</span>
|
|
217
|
+
{cat.missingId > 0 && (
|
|
218
|
+
<button
|
|
219
|
+
onClick={() => onBulkTag(undefined, "definition")}
|
|
220
|
+
disabled={bulkTagStatus === "tagging"}
|
|
221
|
+
className="px-1.5 py-0.5 text-[10px] font-medium text-white bg-violet-500 hover:bg-violet-600 rounded disabled:opacity-50"
|
|
222
|
+
>
|
|
223
|
+
Tag {cat.missingId}
|
|
224
|
+
</button>
|
|
225
|
+
)}
|
|
226
|
+
{isComplete && <CheckCircle className="h-3 w-3 text-green-600" />}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
})()}
|
|
231
|
+
|
|
232
|
+
{/* Inputs */}
|
|
233
|
+
{analysisResult.summary.byCategory?.input && (() => {
|
|
234
|
+
const cat = analysisResult.summary.byCategory.input;
|
|
235
|
+
const isComplete = cat.withId === cat.total && cat.total > 0;
|
|
236
|
+
return (
|
|
237
|
+
<div className={cn(
|
|
238
|
+
"flex items-center justify-between p-2 rounded border",
|
|
239
|
+
isComplete ? "border-green-200 bg-green-50" : "border-gray-200 bg-gray-50"
|
|
240
|
+
)}>
|
|
241
|
+
<div className="flex items-center gap-2">
|
|
242
|
+
<FormInput className={cn("h-3.5 w-3.5", isComplete ? "text-green-600" : "text-orange-600")} />
|
|
243
|
+
<span id="analysis-panel-span-inputs" className="text-xs font-medium">Inputs</span>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="flex items-center gap-2 text-xs">
|
|
246
|
+
<span id="analysis-panel-span-catwithidcattotal" className={isComplete ? "text-green-700" : "text-gray-600"}>{cat.withId}/{cat.total}</span>
|
|
247
|
+
{cat.missingId > 0 && (
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => onBulkTag(undefined, "input")}
|
|
250
|
+
disabled={bulkTagStatus === "tagging"}
|
|
251
|
+
className="px-1.5 py-0.5 text-[10px] font-medium text-white bg-orange-500 hover:bg-orange-600 rounded disabled:opacity-50"
|
|
252
|
+
>
|
|
253
|
+
Tag {cat.missingId}
|
|
254
|
+
</button>
|
|
255
|
+
)}
|
|
256
|
+
{isComplete && <CheckCircle className="h-3 w-3 text-green-600" />}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
})()}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{/* Auto-Tag All Button */}
|
|
265
|
+
{analysisResult.summary.elementsMissingId > 0 && (
|
|
266
|
+
<div className="space-y-2 pt-2 border-t border-gray-200">
|
|
267
|
+
{bulkTagMessage && (
|
|
268
|
+
<div className={cn(
|
|
269
|
+
"p-2 rounded text-xs",
|
|
270
|
+
bulkTagStatus === "complete" && bulkTagMessage.includes("failed")
|
|
271
|
+
? "bg-amber-50 text-amber-700 border border-amber-200"
|
|
272
|
+
: "bg-green-50 text-green-700 border border-green-200"
|
|
273
|
+
)}>
|
|
274
|
+
{bulkTagMessage}
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
|
|
278
|
+
<button
|
|
279
|
+
onClick={() => onBulkTag()}
|
|
280
|
+
disabled={bulkTagStatus === "tagging"}
|
|
281
|
+
className={cn(
|
|
282
|
+
"w-full flex items-center justify-center gap-2 px-3 py-2",
|
|
283
|
+
"text-sm font-medium text-white rounded transition-colors",
|
|
284
|
+
"bg-[#333F48] hover:bg-[#2a343c]",
|
|
285
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
286
|
+
)}
|
|
287
|
+
>
|
|
288
|
+
{bulkTagStatus === "tagging" ? (
|
|
289
|
+
<>
|
|
290
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
291
|
+
Tagging...
|
|
292
|
+
</>
|
|
293
|
+
) : (
|
|
294
|
+
<>
|
|
295
|
+
<Wand2 className="h-4 w-4" />
|
|
296
|
+
Auto-Tag All ({analysisResult.summary.elementsMissingId})
|
|
297
|
+
</>
|
|
298
|
+
)}
|
|
299
|
+
</button>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{/* All Elements Have IDs */}
|
|
304
|
+
{analysisResult.summary.elementsMissingId === 0 && analysisResult.summary.totalElements > 0 && (
|
|
305
|
+
<div className="flex items-center gap-2 p-3 rounded border border-green-200 bg-green-50">
|
|
306
|
+
<CheckCircle className="h-5 w-5 text-green-600 shrink-0" />
|
|
307
|
+
<div>
|
|
308
|
+
<p id="analysis-panel-p-all-elements-have-id" className="text-xs font-medium text-green-700">All elements have IDs!</p>
|
|
309
|
+
<p id="analysis-panel-p-ready-for-individual" className="text-[10px] text-green-600">Ready for individual styling.</p>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{/* Re-scan button */}
|
|
315
|
+
<div className="text-xs text-gray-400 flex items-center justify-between pt-2">
|
|
316
|
+
<span id="analysis-panel-span-scanned-in-analysisr">Scanned in {analysisResult.scanDuration}ms</span>
|
|
317
|
+
<button
|
|
318
|
+
onClick={onRunAnalysis}
|
|
319
|
+
className="text-[#00A3E1] hover:underline"
|
|
320
|
+
>
|
|
321
|
+
Re-scan
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
</>
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|