@sirendesign/markup 1.0.13 → 1.0.14

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,7 @@
1
+ import React from "react";
2
+ import type { FeedbackItem } from "../types";
3
+ interface FeedbackPinsProps {
4
+ onPinClick: (feedback: FeedbackItem) => void;
5
+ }
6
+ export declare const FeedbackPins: React.FC<FeedbackPinsProps>;
7
+ export default FeedbackPins;
@@ -5,3 +5,4 @@ export { FeedbackDetail } from './FeedbackDetail';
5
5
  export { AnnotationOverlay } from './AnnotationOverlay';
6
6
  export { DebugPanel } from './DebugPanel';
7
7
  export { ViewportControls } from './ViewportControls';
8
+ export { FeedbackPins } from './FeedbackPins';
package/dist/index.esm.js CHANGED
@@ -847,6 +847,7 @@ const initialState = {
847
847
  currentUser: null,
848
848
  isAuthenticated: false,
849
849
  viewportMode: null,
850
+ showPins: true,
850
851
  };
851
852
  const useMarkupStore = create()(persist((set, get) => ({
852
853
  ...initialState,
@@ -909,6 +910,7 @@ const useMarkupStore = create()(persist((set, get) => ({
909
910
  activeTab: 'create',
910
911
  activeTool: null,
911
912
  }),
913
+ setShowPins: (show) => set({ showPins: show }),
912
914
  }), {
913
915
  name: 'markup-storage',
914
916
  storage: createJSONStorage(() => localStorage),
@@ -17030,6 +17032,7 @@ const deleteScreenRecording = async (downloadURL) => {
17030
17032
  }
17031
17033
  };
17032
17034
 
17035
+ const MessageIcon = ({ className, ...props }) => (jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }));
17033
17036
  const CloseIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
17034
17037
  const CameraIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }), jsx("circle", { cx: "12", cy: "13", r: "4" })] }));
17035
17038
  const CircleIcon = ({ className, ...props }) => (jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: jsx("circle", { cx: "12", cy: "12", r: "10" }) }));
@@ -17372,7 +17375,7 @@ const AnnotationOverlay = ({ screenshot, onComplete, onCancel, }) => {
17372
17375
 
17373
17376
  const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastClickedElement, lastClickedText, }) => {
17374
17377
  var _a, _b;
17375
- const { config, currentScreenshot, setCurrentScreenshot, annotations, setAnnotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, } = useMarkupStore();
17378
+ const { config, currentScreenshot, setCurrentScreenshot, annotations, setAnnotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, setIsOpen, } = useMarkupStore();
17376
17379
  const [title, setTitle] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.title) || "");
17377
17380
  const [description, setDescription] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.description) || "");
17378
17381
  const [priority, setPriority] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.priority) || "medium");
@@ -17391,12 +17394,48 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17391
17394
  const [isUploadingVideo, setIsUploadingVideo] = useState(false);
17392
17395
  const [mediaRecorder, setMediaRecorder] = useState(null);
17393
17396
  const [recordingStream, setRecordingStream] = useState(null);
17397
+ // Pin placement states
17398
+ const [isPlacingPin, setIsPlacingPin] = useState(false);
17399
+ const [pinPosition, setPinPosition] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.pinLocation)
17400
+ ? {
17401
+ x: initialData.pinLocation.x,
17402
+ y: initialData.pinLocation.y,
17403
+ pageX: initialData.pinLocation.pageX,
17404
+ pageY: initialData.pinLocation.pageY,
17405
+ }
17406
+ : null);
17394
17407
  // Load initial annotations into store if editing
17395
17408
  useEffect(() => {
17396
17409
  if (initialData === null || initialData === void 0 ? void 0 : initialData.annotations) {
17397
17410
  setAnnotations(initialData.annotations);
17398
17411
  }
17399
17412
  }, [initialData, setAnnotations]);
17413
+ // Handle pin placement mode
17414
+ useEffect(() => {
17415
+ if (!isPlacingPin)
17416
+ return;
17417
+ // Close widget during pin placement
17418
+ setIsOpen(false);
17419
+ const handlePinClick = (e) => {
17420
+ e.preventDefault();
17421
+ e.stopPropagation();
17422
+ setPinPosition({
17423
+ x: e.clientX,
17424
+ y: e.clientY,
17425
+ pageX: e.pageX,
17426
+ pageY: e.pageY,
17427
+ });
17428
+ setIsPlacingPin(false);
17429
+ };
17430
+ document.addEventListener('click', handlePinClick, true);
17431
+ document.body.style.cursor = 'crosshair';
17432
+ return () => {
17433
+ document.removeEventListener('click', handlePinClick, true);
17434
+ document.body.style.cursor = '';
17435
+ // Reopen widget when pin placement is done
17436
+ setIsOpen(true);
17437
+ };
17438
+ }, [isPlacingPin, setIsOpen]);
17400
17439
  // Auto-capture selected text when component mounts (only if creating new)
17401
17440
  useEffect(() => {
17402
17441
  var _a;
@@ -17434,12 +17473,12 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17434
17473
  const handleStartRecording = async () => {
17435
17474
  try {
17436
17475
  const stream = await navigator.mediaDevices.getDisplayMedia({
17437
- video: { mediaSource: 'screen' },
17476
+ video: { mediaSource: "screen" },
17438
17477
  audio: false,
17439
17478
  });
17440
17479
  setRecordingStream(stream);
17441
17480
  const recorder = new MediaRecorder(stream, {
17442
- mimeType: 'video/webm;codecs=vp8',
17481
+ mimeType: "video/webm;codecs=vp8",
17443
17482
  });
17444
17483
  const chunks = [];
17445
17484
  recorder.ondataavailable = (e) => {
@@ -17448,14 +17487,14 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17448
17487
  }
17449
17488
  };
17450
17489
  recorder.onstop = () => {
17451
- const blob = new Blob(chunks, { type: 'video/webm' });
17490
+ const blob = new Blob(chunks, { type: "video/webm" });
17452
17491
  // Store blob for upload and create preview URL
17453
17492
  setRecordedBlob(blob);
17454
17493
  const previewURL = URL.createObjectURL(blob);
17455
17494
  setRecordedVideo(previewURL);
17456
17495
  console.log(`Recording saved: ${(blob.size / 1048576).toFixed(2)}MB`);
17457
17496
  // Stop all tracks
17458
- stream.getTracks().forEach(track => track.stop());
17497
+ stream.getTracks().forEach((track) => track.stop());
17459
17498
  setRecordingStream(null);
17460
17499
  };
17461
17500
  recorder.start();
@@ -17463,25 +17502,25 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17463
17502
  setIsRecording(true);
17464
17503
  }
17465
17504
  catch (error) {
17466
- console.error('Failed to start recording:', error);
17467
- alert('Screen recording is not supported or permission was denied.');
17505
+ console.error("Failed to start recording:", error);
17506
+ alert("Screen recording is not supported or permission was denied.");
17468
17507
  }
17469
17508
  };
17470
17509
  const handleStopRecording = () => {
17471
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
17510
+ if (mediaRecorder && mediaRecorder.state !== "inactive") {
17472
17511
  mediaRecorder.stop();
17473
17512
  setIsRecording(false);
17474
17513
  }
17475
17514
  };
17476
17515
  const handleDeleteRecording = () => {
17477
17516
  // Revoke blob URL to free memory
17478
- if (recordedVideo && recordedVideo.startsWith('blob:')) {
17517
+ if (recordedVideo && recordedVideo.startsWith("blob:")) {
17479
17518
  URL.revokeObjectURL(recordedVideo);
17480
17519
  }
17481
17520
  setRecordedVideo(undefined);
17482
17521
  setRecordedBlob(null);
17483
17522
  if (recordingStream) {
17484
- recordingStream.getTracks().forEach(track => track.stop());
17523
+ recordingStream.getTracks().forEach((track) => track.stop());
17485
17524
  setRecordingStream(null);
17486
17525
  }
17487
17526
  };
@@ -17539,11 +17578,11 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17539
17578
  setIsUploadingVideo(true);
17540
17579
  const tempId = generateId();
17541
17580
  screenRecordingURL = await uploadScreenRecording(recordedBlob, tempId);
17542
- console.log('Video uploaded to Storage:', screenRecordingURL);
17581
+ console.log("Video uploaded to Storage:", screenRecordingURL);
17543
17582
  }
17544
17583
  catch (error) {
17545
- console.error('Failed to upload video:', error);
17546
- alert('Failed to upload screen recording. Please try again.');
17584
+ console.error("Failed to upload video:", error);
17585
+ alert("Failed to upload screen recording. Please try again.");
17547
17586
  setIsSubmitting(false);
17548
17587
  setIsUploadingVideo(false);
17549
17588
  return;
@@ -17552,10 +17591,13 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17552
17591
  setIsUploadingVideo(false);
17553
17592
  }
17554
17593
  }
17594
+ const pageWidth = document.documentElement.scrollWidth;
17595
+ const pageHeight = document.documentElement.scrollHeight;
17555
17596
  const feedback = {
17556
17597
  id: (initialData === null || initialData === void 0 ? void 0 : initialData.id) || generateId(),
17557
17598
  type: feedbackType,
17558
- title: title.trim() || (feedbackType === "copy-amendment" ? "Copy Amendment" : ""),
17599
+ title: title.trim() ||
17600
+ (feedbackType === "copy-amendment" ? "Copy Amendment" : ""),
17559
17601
  description: description.trim(),
17560
17602
  currentCopy: feedbackType === "copy-amendment"
17561
17603
  ? currentCopy.trim()
@@ -17565,10 +17607,12 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17565
17607
  annotations: annotations,
17566
17608
  status: (initialData === null || initialData === void 0 ? void 0 : initialData.status) || "open",
17567
17609
  priority,
17568
- pageMetadata: (initialData === null || initialData === void 0 ? void 0 : initialData.pageMetadata) || getPageMetadata(lastClickedElement || undefined),
17610
+ pageMetadata: (initialData === null || initialData === void 0 ? void 0 : initialData.pageMetadata) ||
17611
+ getPageMetadata(lastClickedElement || undefined),
17569
17612
  createdAt: (initialData === null || initialData === void 0 ? void 0 : initialData.createdAt) || new Date().toISOString(),
17570
17613
  updatedAt: new Date().toISOString(),
17571
- createdBy: (initialData === null || initialData === void 0 ? void 0 : initialData.createdBy) || currentUser || {
17614
+ createdBy: (initialData === null || initialData === void 0 ? void 0 : initialData.createdBy) ||
17615
+ currentUser || {
17572
17616
  id: ((_a = config.user) === null || _a === void 0 ? void 0 : _a.id) || "anonymous",
17573
17617
  name: ((_b = config.user) === null || _b === void 0 ? void 0 : _b.name) || "Anonymous",
17574
17618
  email: ((_c = config.user) === null || _c === void 0 ? void 0 : _c.email) || "",
@@ -17579,6 +17623,22 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17579
17623
  sessionEvents: getSessionEvents ? getSessionEvents() : undefined,
17580
17624
  designMockup,
17581
17625
  screenRecording: screenRecordingURL,
17626
+ pinLocation: pinPosition
17627
+ ? {
17628
+ x: pinPosition.x,
17629
+ y: pinPosition.y,
17630
+ pageX: pinPosition.pageX,
17631
+ pageY: pinPosition.pageY,
17632
+ viewportWidth: window.innerWidth,
17633
+ viewportHeight: window.innerHeight,
17634
+ pageWidth,
17635
+ pageHeight,
17636
+ scrollX: window.scrollX,
17637
+ scrollY: window.scrollY,
17638
+ percentX: pageWidth ? (pinPosition.pageX / pageWidth) * 100 : undefined,
17639
+ percentY: pageHeight ? (pinPosition.pageY / pageHeight) * 100 : undefined,
17640
+ }
17641
+ : undefined,
17582
17642
  };
17583
17643
  try {
17584
17644
  if (config.onSubmit) {
@@ -17603,6 +17663,10 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17603
17663
  if (isAnnotating && currentScreenshot) {
17604
17664
  return (jsx(AnnotationOverlay, { screenshot: currentScreenshot, onComplete: handleAnnotationComplete, onCancel: handleAnnotationCancel }));
17605
17665
  }
17666
+ // Show pin placement overlay
17667
+ if (isPlacingPin) {
17668
+ return (jsx("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-[999999] pointer-events-none", children: jsxs("div", { className: "bg-white rounded-2xl shadow-2xl p-6 max-w-md mx-4 text-center border-2 border-orange-500", children: [jsx("div", { className: "w-12 h-12 mx-auto mb-3 bg-orange-100 rounded-full flex items-center justify-center", children: jsxs("svg", { className: "w-6 h-6 text-orange-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })] }) }), jsx("h3", { className: "text-lg font-bold text-gray-900 mb-2", children: "Click to Place Pin" }), jsx("p", { className: "text-gray-600 text-sm mb-3", children: "Click anywhere on the page to mark the location" }), jsx("button", { type: "button", onClick: () => setIsPlacingPin(false), className: "px-4 py-2 text-sm font-medium text-white bg-orange-600 hover:bg-orange-700 rounded-lg transition-colors pointer-events-auto", children: "Cancel" })] }) }));
17669
+ }
17606
17670
  return (jsxs("form", { onSubmit: handleSubmit, children: [jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Type" }), jsx("div", { className: "flex gap-2", children: [
17607
17671
  { id: "general", label: "General" },
17608
17672
  { id: "copy-amendment", label: "Copy Change" },
@@ -17610,10 +17674,14 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17610
17674
  ? "bg-[#C2D1D9] text-black shadow-md shadow-[#C2D1D9]/30"
17611
17675
  : "bg-gray-100 text-gray-700 hover:bg-gray-200 hover:shadow-sm"), children: type.label }, type.id))) })] }), feedbackType === "copy-amendment" && (jsxs(Fragment, { children: [jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Current Copy" }), jsx("textarea", { placeholder: "Select text on page or paste current copy here...", value: currentCopy, onChange: (e) => setCurrentCopy(e.target.value), className: "w-full px-4 py-3 bg-red-50 border border-red-200 rounded-xl text-sm min-h-[80px] resize-y focus:outline-none focus:bg-white focus:border-red-500 focus:shadow-lg focus:shadow-red-500/10 transition-all" })] }), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "New Copy *" }), jsx("textarea", { placeholder: "Enter the replacement text...", value: newCopy, onChange: (e) => setNewCopy(e.target.value), required: feedbackType === "copy-amendment", className: "w-full px-4 py-3 bg-green-50 border border-green-200 rounded-xl text-sm min-h-[80px] resize-y focus:outline-none focus:bg-white focus:border-green-500 focus:shadow-lg focus:shadow-green-500/10 transition-all" })] })] })), feedbackType !== "copy-amendment" && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Title *" }), jsx("input", { type: "text", placeholder: "Brief description of the issue", value: title, onChange: (e) => setTitle(e.target.value), required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm focus:outline-none focus:bg-white focus:border-[#C2D1D9] focus:shadow-lg focus:shadow-[#C2D1D9]/10 transition-all" })] })), feedbackType !== "copy-amendment" && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Description" }), jsx("textarea", { placeholder: "Provide more details about the issue...", value: description, onChange: (e) => setDescription(e.target.value), required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm focus:outline-none focus:bg-white focus:border-[#C2D1D9] focus:shadow-lg focus:shadow-[#C2D1D9]/10 transition-all min-h-[100px] resize-y" })] })), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Priority" }), jsx("div", { className: "flex gap-2", children: Object.keys(priorityColors).map((p) => (jsx("div", { onClick: () => setPriority(p), className: cn("flex-1 py-2 px-2 rounded-xl text-xs font-medium cursor-pointer transition-all text-center capitalize border border-transparent", priority === p
17612
17676
  ? cn(priorityColors[p], "border-transparent shadow-sm")
17613
- : "bg-gray-100 text-gray-600 hover:bg-gray-200"), children: p }, p))) })] }), annotatedScreenshot || currentScreenshot ? (jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md mb-5 group", children: [jsx("img", { src: annotatedScreenshot || currentScreenshot || "", alt: "Screenshot", className: "w-full block" }), jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" }), jsxs("div", { className: "absolute top-3 right-3 flex gap-2", children: [jsxs("button", { type: "button", onClick: handleEditScreenshot, className: "bg-white text-gray-700 border-none px-3 py-1.5 rounded-lg cursor-pointer text-xs font-medium flex items-center gap-1.5 hover:bg-gray-100 shadow-lg transition-all", children: [jsx(EditIcon, { className: "w-3.5 h-3.5" }), " Edit"] }), jsxs("button", { type: "button", onClick: handleRemoveScreenshot, className: "bg-white text-red-600 border-none px-3 py-1.5 rounded-lg cursor-pointer text-xs font-medium flex items-center gap-1.5 hover:bg-red-50 shadow-lg transition-all", children: [jsx(TrashIcon, { className: "w-3.5 h-3.5" }), " Remove"] })] })] })) : (jsxs("div", { className: "flex gap-3 mb-5", children: [jsx("div", { onClick: handleCaptureScreenshot, className: "flex-1 p-4 bg-white border-2 border-dashed border-gray-300 rounded-xl cursor-pointer flex items-center justify-center gap-2.5 text-gray-600 text-sm font-medium hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isCapturing ? (jsxs(Fragment, { children: [jsx("span", { className: "w-5 h-5 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" }), "Capturing..."] })) : (jsxs(Fragment, { children: [jsx(CameraIcon, { className: "w-5 h-5" }), " Screenshot"] })) }), jsx("div", { onClick: isRecording ? handleStopRecording : handleStartRecording, className: "flex-1 p-4 bg-white border-2 border-dashed border-gray-300 rounded-xl cursor-pointer flex items-center justify-center gap-2.5 text-gray-600 text-sm font-medium hover:border-red-400 hover:text-red-600 hover:bg-red-50 hover:shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isRecording ? (jsxs(Fragment, { children: [jsx("span", { className: "w-2.5 h-2.5 bg-red-600 rounded-sm animate-pulse" }), "Stop Recording"] })) : (jsxs(Fragment, { children: [jsxs("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsx("circle", { cx: "12", cy: "12", r: "10", strokeWidth: "2" }), jsx("circle", { cx: "12", cy: "12", r: "4", fill: "currentColor" })] }), "Record Screen"] })) })] })), recordedVideo && (jsxs("div", { className: "mb-5 relative rounded-xl overflow-hidden shadow-md border border-gray-200", children: [jsx("video", { src: recordedVideo, controls: true, className: "w-full h-auto" }), jsx("button", { type: "button", onClick: handleDeleteRecording, className: "absolute top-2 right-2 bg-white text-red-500 p-1.5 rounded-lg shadow hover:bg-gray-100 cursor-pointer border-none", children: jsx(TrashIcon, { className: "w-3.5 h-3.5" }) })] })), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Design Mockup" }), designMockup ? (jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md group border border-gray-200", children: [jsx("img", { src: designMockup, className: "w-full h-32 object-cover", alt: "Mockup" }), jsx("button", { type: "button", onClick: () => setDesignMockup(undefined), className: "absolute top-2 right-2 bg-white text-red-500 p-1.5 rounded-lg shadow hover:bg-gray-100 cursor-pointer border-none", children: jsx(TrashIcon, { className: "w-3.5 h-3.5" }) })] })) : (jsxs("div", { className: "border-2 border-dashed border-gray-200 rounded-xl p-4 bg-gray-50 flex items-center justify-center relative hover:bg-white hover:border-[#C2D1D9] transition-all", children: [jsx("input", { type: "file", accept: "image/*", onChange: handleMockupUpload, className: "absolute inset-0 opacity-0 cursor-pointer w-full h-full" }), jsxs("span", { className: "text-sm text-gray-500 flex items-center gap-2 font-medium", children: [jsx(PlusIcon, { className: "w-4 h-4" }), " Upload Mockup"] })] }))] }), config.availableTags && config.availableTags.length > 0 && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Tags" }), jsx("div", { className: "flex flex-wrap gap-2", children: config.availableTags.map((tag) => (jsx("div", { onClick: () => toggleTag(tag), className: cn("px-3 py-1.5 rounded-full text-xs font-medium cursor-pointer transition-all", selectedTags.includes(tag)
17677
+ : "bg-gray-100 text-gray-600 hover:bg-gray-200"), children: p }, p))) })] }), annotatedScreenshot || currentScreenshot ? (jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md mb-5 group", children: [jsx("img", { src: annotatedScreenshot || currentScreenshot || "", alt: "Screenshot", className: "w-full block" }), jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" }), jsxs("div", { className: "absolute top-3 right-3 flex gap-2", children: [jsxs("button", { type: "button", onClick: handleEditScreenshot, className: "bg-white text-gray-700 border-none px-3 py-1.5 rounded-lg cursor-pointer text-xs font-medium flex items-center gap-1.5 hover:bg-gray-100 shadow-lg transition-all", children: [jsx(EditIcon, { className: "w-3.5 h-3.5" }), " Edit"] }), jsxs("button", { type: "button", onClick: handleRemoveScreenshot, className: "bg-white text-red-600 border-none px-3 py-1.5 rounded-lg cursor-pointer text-xs font-medium flex items-center gap-1.5 hover:bg-red-50 shadow-lg transition-all", children: [jsx(TrashIcon, { className: "w-3.5 h-3.5" }), " Remove"] })] })] })) : (jsxs("div", { className: "flex gap-3 mb-5", children: [jsx("div", { onClick: handleCaptureScreenshot, className: "flex-1 p-4 bg-white border-2 border-dashed border-gray-300 rounded-xl cursor-pointer flex items-center justify-center gap-2.5 text-gray-600 text-sm font-medium hover:border-indigo-400 hover:text-indigo-600 hover:bg-indigo-50 hover:shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isCapturing ? (jsxs(Fragment, { children: [jsx("span", { className: "w-5 h-5 border-2 border-indigo-200 border-t-indigo-600 rounded-full animate-spin" }), "Capturing..."] })) : (jsxs(Fragment, { children: [jsx(CameraIcon, { className: "w-5 h-5" }), " Screenshot"] })) }), jsx("div", { onClick: isRecording
17678
+ ? handleStopRecording
17679
+ : handleStartRecording, className: "flex-1 p-4 bg-white border-2 border-dashed border-gray-300 rounded-xl cursor-pointer flex items-center justify-center gap-2.5 text-gray-600 text-sm font-medium hover:border-red-400 hover:text-red-600 hover:bg-red-50 hover:shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed", children: isRecording ? (jsxs(Fragment, { children: [jsx("span", { className: "w-2.5 h-2.5 bg-red-600 rounded-sm animate-pulse" }), "Stop Recording"] })) : (jsxs(Fragment, { children: [jsxs("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsx("circle", { cx: "12", cy: "12", r: "10", strokeWidth: "2" }), jsx("circle", { cx: "12", cy: "12", r: "4", fill: "currentColor" })] }), "Record Screen"] })) })] })), jsxs("div", { className: "mb-5", children: [jsxs("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: ["Pin Location", " ", !pinPosition && jsx("span", { className: "text-red-500", children: "*" })] }), pinPosition ? (jsxs("div", { className: "flex items-center gap-3 p-3 bg-green-50 border border-green-200 rounded-xl", children: [jsx("div", { className: "w-8 h-8 rounded-full bg-green-500 flex items-center justify-center shadow-md", children: jsx("svg", { className: "w-4 h-4 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M5 13l4 4L19 7" }) }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "text-sm font-medium text-gray-800", children: "Pin placed on page" }), jsxs("div", { className: "text-xs text-gray-600", children: ["Position: (", Math.round(pinPosition.pageX), ",", " ", Math.round(pinPosition.pageY), ")"] })] }), jsx("div", { onClick: () => setIsPlacingPin(true), className: "px-3 py-1.5 text-xs font-medium text-indigo-600 hover:text-indigo-700 hover:bg-indigo-50 rounded-lg transition-all", children: "Re-place" })] })) : (jsxs("div", { onClick: () => setIsPlacingPin(true), className: "p-4 bg-white border-2 border-dashed border-orange-300 rounded-xl cursor-pointer flex items-center justify-center gap-2.5 text-orange-600 text-sm font-medium hover:border-orange-400 hover:bg-orange-50 hover:shadow-md transition-all", children: [jsxs("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })] }), "Click to place pin on page"] }))] }), recordedVideo && (jsxs("div", { className: "mb-5 relative rounded-xl overflow-hidden shadow-md border border-gray-200", children: [jsx("video", { src: recordedVideo, controls: true, className: "w-full h-auto" }), jsx("button", { type: "button", onClick: handleDeleteRecording, className: "absolute top-2 right-2 bg-white text-red-500 p-1.5 rounded-lg shadow hover:bg-gray-100 cursor-pointer border-none", children: jsx(TrashIcon, { className: "w-3.5 h-3.5" }) })] })), jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Design Mockup" }), designMockup ? (jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md group border border-gray-200", children: [jsx("img", { src: designMockup, className: "w-full h-32 object-cover", alt: "Mockup" }), jsx("button", { type: "button", onClick: () => setDesignMockup(undefined), className: "absolute top-2 right-2 bg-white text-red-500 p-1.5 rounded-lg shadow hover:bg-gray-100 cursor-pointer border-none", children: jsx(TrashIcon, { className: "w-3.5 h-3.5" }) })] })) : (jsxs("div", { className: "border-2 border-dashed border-gray-200 rounded-xl p-4 bg-gray-50 flex items-center justify-center relative hover:bg-white hover:border-[#C2D1D9] transition-all", children: [jsx("input", { type: "file", accept: "image/*", onChange: handleMockupUpload, className: "absolute inset-0 opacity-0 cursor-pointer w-full h-full" }), jsxs("span", { className: "text-sm text-gray-500 flex items-center gap-2 font-medium", children: [jsx(PlusIcon, { className: "w-4 h-4" }), " Upload Mockup"] })] }))] }), config.availableTags && config.availableTags.length > 0 && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-sm font-semibold mb-2 text-gray-800", children: "Tags" }), jsx("div", { className: "flex flex-wrap gap-2", children: config.availableTags.map((tag) => (jsx("div", { onClick: () => toggleTag(tag), className: cn("px-3 py-1.5 rounded-full text-xs font-medium cursor-pointer transition-all", selectedTags.includes(tag)
17614
17680
  ? "bg-[#C2D1D9] text-white shadow-md shadow-[#C2D1D9]/30"
17615
17681
  : "bg-gray-100 text-gray-700 hover:bg-gray-200 hover:shadow-sm"), children: tag }, tag))) })] })), jsxs("div", { className: "flex gap-3 mt-6 pt-6 border-t border-gray-100", children: [jsx("div", { onClick: onCancel, className: "flex-1 py-3 px-5 rounded-xl text-sm font-semibold cursor-pointer transition-all bg-gray-100 text-gray-700 hover:bg-gray-200 hover:shadow-md text-center", children: ((_a = config.labels) === null || _a === void 0 ? void 0 : _a.cancelButton) || "Cancel" }), jsx("div", { onClick: (e) => {
17616
- const isValid = feedbackType === "copy-amendment" ? newCopy.trim() : title.trim();
17682
+ const isValid = feedbackType === "copy-amendment"
17683
+ ? newCopy.trim()
17684
+ : title.trim();
17617
17685
  if (!isValid || isSubmitting)
17618
17686
  return;
17619
17687
  const form = e.currentTarget.closest("form");
@@ -17624,12 +17692,18 @@ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastC
17624
17692
  });
17625
17693
  form.dispatchEvent(submitEvent);
17626
17694
  }
17627
- }, className: cn("flex-1 py-3 px-5 rounded-xl text-sm font-semibold cursor-pointer transition-all text-white border-none hover:shadow-lg text-center", ((feedbackType === "copy-amendment" ? !newCopy.trim() : !title.trim()) || isSubmitting) &&
17695
+ }, className: cn("flex-1 py-3 px-5 rounded-xl text-sm font-semibold cursor-pointer transition-all text-white border-none hover:shadow-lg text-center", ((feedbackType === "copy-amendment"
17696
+ ? !newCopy.trim()
17697
+ : !title.trim()) ||
17698
+ isSubmitting) &&
17628
17699
  "opacity-50 cursor-not-allowed"), style: {
17629
- backgroundColor: (feedbackType === "copy-amendment" ? !newCopy.trim() : !title.trim()) || isSubmitting
17700
+ backgroundColor: (feedbackType === "copy-amendment"
17701
+ ? !newCopy.trim()
17702
+ : !title.trim()) || isSubmitting
17630
17703
  ? "#9ca3af"
17631
17704
  : "green-500",
17632
- }, children: isSubmitting ? (isUploadingVideo ? (jsxs("span", { className: "flex items-center justify-center gap-2", children: [jsx("span", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" }), jsx("span", { children: "Uploading video..." })] })) : (jsx("span", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin mx-auto block" }))) : (((_b = config.labels) === null || _b === void 0 ? void 0 : _b.submitButton) || (initialData ? "Update Feedback" : "Submit Feedback")) })] })] }));
17705
+ }, children: isSubmitting ? (isUploadingVideo ? (jsxs("span", { className: "flex items-center justify-center gap-2", children: [jsx("span", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" }), jsx("span", { children: "Uploading video..." })] })) : (jsx("span", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin mx-auto block" }))) : (((_b = config.labels) === null || _b === void 0 ? void 0 : _b.submitButton) ||
17706
+ (initialData ? "Update Feedback" : "Submit Feedback")) })] })] }));
17633
17707
  };
17634
17708
 
17635
17709
  const statusOptions$1 = [
@@ -39762,6 +39836,7 @@ const subscribeFeedback = (projectId, callback) => {
39762
39836
  componentName: data.componentName,
39763
39837
  designMockup: data.designMockup,
39764
39838
  screenRecording: data.screenRecording,
39839
+ pinLocation: data.pinLocation,
39765
39840
  };
39766
39841
  });
39767
39842
  callback(feedbackItems);
@@ -39870,6 +39945,7 @@ const FeedbackDetail = ({ feedback: initialFeedback, onBack, onDelete, onUpdate,
39870
39945
  const [showFullScreenshot, setShowFullScreenshot] = useState(false);
39871
39946
  const [copied, setCopied] = useState(false);
39872
39947
  const [isEditing, setIsEditing] = useState(false);
39948
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
39873
39949
  // Feature 2 & 5 states
39874
39950
  const [showReplay, setShowReplay] = useState(false);
39875
39951
  const [showOverlay, setShowOverlay] = useState(false);
@@ -40023,9 +40099,8 @@ ${feedback.comments && feedback.comments.length > 0
40023
40099
  setNewComment("");
40024
40100
  setIsAddingComment(false);
40025
40101
  };
40026
- const handleDelete = async () => {
40027
- if (!confirm("Are you sure you want to delete this feedback?"))
40028
- return;
40102
+ const handleDelete = () => setShowDeleteConfirm(true);
40103
+ const handleConfirmDelete = async () => {
40029
40104
  // Delete from Firebase if initialized
40030
40105
  try {
40031
40106
  await deleteFeedback(feedback.id);
@@ -40039,8 +40114,10 @@ ${feedback.comments && feedback.comments.length > 0
40039
40114
  if (onDelete) {
40040
40115
  onDelete(feedback.id);
40041
40116
  }
40117
+ setShowDeleteConfirm(false);
40042
40118
  };
40043
- return (jsxs("div", { className: "flex flex-col h-full px-5 overflow-y-auto", children: [jsxs("div", { className: "flex items-center justify-between pb-3 mb-3 border-b border-gray-200", children: [jsxs("div", { onClick: onBack, className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: [jsx(ArrowLeftIcon, { className: "w-4 h-4" }), "Back"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsx("div", { onClick: handleCopy, className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: copied ? (jsxs(Fragment, { children: [jsx(CheckIcon, { className: "w-4 h-4" }), "Copied!"] })) : (jsx(Fragment, { children: jsx(CopyIcon, { className: "w-4 h-4" }) })) }), canEdit && (jsx("div", { onClick: () => setIsEditing(true), className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: jsx(EditIcon, { className: "w-4 h-4" }) })), canDelete && onDelete && (jsx("div", { onClick: handleDelete, className: "flex items-center gap-1.5 text-sm text-red-600 bg-transparent border-none cursor-pointer hover:text-red-700 transition-colors", children: jsx(TrashIcon, { className: "w-4 h-4" }) }))] })] }), jsxs("div", { className: "flex-1 pr-1", children: [jsxs("div", { className: "mb-4", children: [jsxs("div", { className: "flex items-start gap-2 mb-2", children: [jsx("h3", { className: "text-base font-semibold text-gray-900 m-0 flex-1", children: feedback.title }), ((_b = feedback.pageMetadata) === null || _b === void 0 ? void 0 : _b.deviceType) && (jsxs("div", { className: "flex-shrink-0 mt-0.5", children: [feedback.pageMetadata.deviceType ===
40119
+ const handleCancelDelete = () => setShowDeleteConfirm(false);
40120
+ return (jsxs("div", { className: "flex flex-col h-full px-5 overflow-y-auto", children: [jsxs("div", { className: "flex items-center justify-between pb-3 mb-3 border-b border-gray-200", children: [jsxs("div", { onClick: onBack, className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: [jsx(ArrowLeftIcon, { className: "w-4 h-4" }), "Back"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsx("div", { onClick: handleCopy, className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: copied ? (jsxs(Fragment, { children: [jsx(CheckIcon, { className: "w-4 h-4" }), "Copied!"] })) : (jsx(Fragment, { children: jsx(CopyIcon, { className: "w-4 h-4" }) })) }), canEdit && (jsx("div", { onClick: () => setIsEditing(true), className: "flex items-center gap-1.5 text-sm text-gray-600 bg-transparent border-none cursor-pointer hover:text-gray-900 transition-colors", children: jsx(EditIcon, { className: "w-4 h-4" }) })), canDelete && onDelete && (jsx("div", { onClick: handleDelete, className: "flex items-center gap-1.5 text-sm text-red-600 bg-transparent border-none cursor-pointer hover:text-red-700 transition-colors", children: jsx(TrashIcon, { className: "w-4 h-4" }) }))] })] }), showDeleteConfirm && (jsxs("div", { className: "mb-3 p-2 rounded-xl border border-red-200 bg-red-50 text-red-800 flex items-center gap-3", children: [jsx("div", { className: "flex-1 text-xs font-medium", children: "Delete this feedback?" }), jsx("div", { onClick: handleCancelDelete, className: "px-2 py-1.5 text-xs font-semibold text-red-700 rounded-lg hover:bg-red-100 cursor-pointer", children: "Cancel" }), jsx("div", { onClick: handleConfirmDelete, className: "px-2 py-1.5 text-xs font-semibold text-white bg-red-600 rounded-lg hover:bg-red-700 cursor-pointer", children: "Delete" })] })), jsxs("div", { className: "flex-1 pr-1", children: [jsxs("div", { className: "mb-4", children: [jsxs("div", { className: "flex items-start gap-2 mb-2", children: [jsx("h3", { className: "text-base font-semibold text-gray-900 m-0 flex-1", children: feedback.title }), ((_b = feedback.pageMetadata) === null || _b === void 0 ? void 0 : _b.deviceType) && (jsxs("div", { className: "flex-shrink-0 mt-0.5", children: [feedback.pageMetadata.deviceType ===
40044
40121
  "desktop" && (jsx(DesktopIcon, { className: "w-5 h-5 text-gray-500", title: "Desktop" })), feedback.pageMetadata.deviceType ===
40045
40122
  "tablet" && (jsx(TabletIcon, { className: "w-5 h-5 text-gray-500", title: "Tablet" })), feedback.pageMetadata.deviceType ===
40046
40123
  "mobile" && (jsx(MobileIcon, { className: "w-5 h-5 text-gray-500", title: "Mobile" }))] }))] }), jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [feedback.type && (jsx("span", { className: cn("px-2 py-0.5 rounded-full text-[10px] font-medium uppercase", feedback.type === "copy-amendment"
@@ -40221,6 +40298,89 @@ const ViewportControls = () => {
40221
40298
  : "hover:bg-gray-100"), children: [jsx(DesktopIcon, { className: cn("w-4 h-4 transition-colors", isActivePreset(preset) ? "text-blue-600" : "text-gray-400 group-hover:text-gray-600") }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "text-sm font-medium text-gray-700 group-hover:text-gray-900", children: preset.name }), jsxs("div", { className: "text-xs text-gray-500", children: [preset.width, " \u00D7 ", preset.height] })] })] }, preset.name)))] }) }))] }));
40222
40299
  };
40223
40300
 
40301
+ const FeedbackPins = ({ onPinClick }) => {
40302
+ const { feedbackItems, showPins } = useMarkupStore();
40303
+ const [docSize, setDocSize] = useState({ width: 0, height: 0 });
40304
+ useEffect(() => {
40305
+ const measure = () => {
40306
+ const root = document.documentElement;
40307
+ const body = document.body;
40308
+ const width = root.clientWidth || body.clientWidth || window.innerWidth;
40309
+ const height = Math.max(root.scrollHeight, body.scrollHeight, root.clientHeight, body.clientHeight);
40310
+ setDocSize({ width, height });
40311
+ };
40312
+ measure();
40313
+ const resizeObserver = new ResizeObserver(() => measure());
40314
+ resizeObserver.observe(document.documentElement);
40315
+ resizeObserver.observe(document.body);
40316
+ const handleResize = () => measure();
40317
+ const handleScroll = () => measure();
40318
+ window.addEventListener('resize', handleResize);
40319
+ window.addEventListener('scroll', handleScroll, { passive: true });
40320
+ return () => {
40321
+ resizeObserver.disconnect();
40322
+ window.removeEventListener('resize', handleResize);
40323
+ window.removeEventListener('scroll', handleScroll);
40324
+ };
40325
+ }, []);
40326
+ // Filter pins for current page URL
40327
+ const currentPagePins = useMemo(() => {
40328
+ const currentUrl = window.location.href;
40329
+ return feedbackItems.filter(item => {
40330
+ var _a;
40331
+ // Only show pins with pinLocation data
40332
+ if (!item.pinLocation)
40333
+ return false;
40334
+ // Only show pins from the same page
40335
+ if (((_a = item.pageMetadata) === null || _a === void 0 ? void 0 : _a.url) !== currentUrl)
40336
+ return false;
40337
+ return true;
40338
+ });
40339
+ }, [feedbackItems]);
40340
+ // Don't show pins if toggle is off
40341
+ if (!showPins)
40342
+ return null;
40343
+ if (currentPagePins.length === 0)
40344
+ return null;
40345
+ const containerStyle = {
40346
+ position: 'absolute',
40347
+ top: 0,
40348
+ left: 0,
40349
+ width: docSize.width || '100%',
40350
+ height: docSize.height || '100%',
40351
+ pointerEvents: 'none',
40352
+ zIndex: 999998,
40353
+ };
40354
+ return (jsx("div", { className: "feedback-pins-container", "data-markup-pins": true, style: containerStyle, children: currentPagePins.map((feedback) => {
40355
+ const { pinLocation } = feedback;
40356
+ if (!pinLocation)
40357
+ return null;
40358
+ const pageWidth = docSize.width || pinLocation.pageWidth || pinLocation.viewportWidth || window.innerWidth;
40359
+ const pageHeight = docSize.height || pinLocation.pageHeight || pinLocation.viewportHeight || window.innerHeight;
40360
+ // Prefer stored percentages; fall back to computing from stored absolute coords
40361
+ const leftPercent = typeof pinLocation.percentX === 'number'
40362
+ ? pinLocation.percentX
40363
+ : pageWidth ? (pinLocation.pageX / pageWidth) * 100 : 0;
40364
+ const topPercent = typeof pinLocation.percentY === 'number'
40365
+ ? pinLocation.percentY
40366
+ : pageHeight ? (pinLocation.pageY / pageHeight) * 100 : 0;
40367
+ const style = {
40368
+ position: 'absolute',
40369
+ left: `${leftPercent}%`,
40370
+ top: `${topPercent}%`,
40371
+ transform: 'translate(-50%, -50%)',
40372
+ pointerEvents: 'auto',
40373
+ };
40374
+ return (jsx("div", { className: "feedback-pin-wrapper", style: style, onClick: () => onPinClick(feedback), children: jsxs("div", { className: "feedback-pin group relative cursor-pointer", children: [jsx("div", { className: `
40375
+ w-8 h-8 rounded-full flex items-center justify-center
40376
+ shadow-lg hover:shadow-xl transition-all
40377
+ ${feedback.status === 'open' ? 'bg-[#E6B6CF] hover:bg-[#d9a3c0]' : 'bg-green-500 hover:bg-green-600'}
40378
+ border-2 border-white
40379
+ hover:scale-110
40380
+ `, children: jsx(MessageIcon, { className: "w-4 h-4 text-white" }) }), jsxs("div", { className: "\n absolute bottom-full left-1/2 -translate-x-1/2 mb-2\n opacity-0 group-hover:opacity-100\n pointer-events-none\n transition-opacity duration-200\n bg-gray-900 text-white text-xs rounded-lg py-2 px-3\n whitespace-nowrap shadow-xl\n max-w-[200px]\n ", children: [jsx("div", { className: "font-semibold truncate", children: feedback.title || 'Feedback' }), jsx("div", { className: "text-gray-300 text-[10px] mt-0.5", children: "Click to view" }), jsx("div", { className: "\n absolute top-full left-1/2 -translate-x-1/2\n w-0 h-0\n border-l-4 border-l-transparent\n border-r-4 border-r-transparent\n border-t-4 border-t-gray-900\n " })] })] }) }, feedback.id));
40381
+ }) }));
40382
+ };
40383
+
40224
40384
  const useFirebaseSync = () => {
40225
40385
  const config = useMarkupStore((state) => state.config);
40226
40386
  const setCurrentUser = useMarkupStore((state) => state.setCurrentUser);
@@ -40432,7 +40592,7 @@ const MarkupWidget = ({ config: userConfig, }) => {
40432
40592
  console.error('Failed to initialize Firebase:', error);
40433
40593
  }
40434
40594
  }
40435
- const { isOpen, setIsOpen, activeTab, setActiveTab, selectedFeedback, setSelectedFeedback, feedbackItems, addFeedbackItem, removeFeedbackItem, updateFeedback, config, setConfig, reset, isAuthenticated, currentUser, viewportMode, setViewportMode, } = useMarkupStore();
40595
+ const { isOpen, setIsOpen, activeTab, setActiveTab, selectedFeedback, setSelectedFeedback, feedbackItems, addFeedbackItem, removeFeedbackItem, updateFeedback, config, setConfig, reset, isAuthenticated, currentUser, viewportMode, setViewportMode, showPins, setShowPins, } = useMarkupStore();
40436
40596
  // Initialize Firebase sync
40437
40597
  useFirebaseSync();
40438
40598
  // Initialize Session Recording (Feature 2)
@@ -40571,6 +40731,11 @@ const MarkupWidget = ({ config: userConfig, }) => {
40571
40731
  const handleSelectFeedback = useCallback((feedback) => {
40572
40732
  setSelectedFeedback(feedback);
40573
40733
  }, [setSelectedFeedback]);
40734
+ const handlePinClick = useCallback((feedback) => {
40735
+ setIsOpen(true);
40736
+ setActiveTab("list");
40737
+ setSelectedFeedback(feedback);
40738
+ }, [setIsOpen, setActiveTab, setSelectedFeedback]);
40574
40739
  const handleBackFromDetail = useCallback(() => {
40575
40740
  setSelectedFeedback(null);
40576
40741
  setViewportMode(null);
@@ -40614,11 +40779,14 @@ const MarkupWidget = ({ config: userConfig, }) => {
40614
40779
  ? adjustColor(config.primaryColor, -20)
40615
40780
  : "#4f46e5",
40616
40781
  });
40617
- return (jsxs(Fragment, { children: [jsx(ViewportControls, {}), jsxs("div", { className: cn("markup-widget fixed z-[999999] right-0 top-1/2 flex items-center transition-transform duration-300 ease-in-out", isOpen ? "" : "hover:translate-x-[-8px]"), style: {
40782
+ return (jsxs(Fragment, { children: [jsx(FeedbackPins, { onPinClick: handlePinClick }), jsx(ViewportControls, {}), jsxs("div", { className: cn("markup-widget fixed z-[999999] right-0 top-1/2 flex items-center transition-transform duration-300 ease-in-out", isOpen ? "" : "hover:translate-x-[-8px]"), style: {
40618
40783
  transform: isOpen
40619
40784
  ? "translateY(-50%) translateX(0)"
40620
40785
  : "translateY(-50%) translateX(calc(100% - 40px))",
40621
- }, "data-markup-widget": true, children: [jsxs("div", { onClick: handleToggle, className: "relative bg-white h-fit flex items-center gap-2 px-2 pr-5 text-black border-none rounded-l-2xl cursor-pointer text-sm font-semibold transition-all py-5 shadow-lg", children: [isOpen && jsx(CloseIcon, { className: "w-5 h-5" }), jsx("span", { className: "text-black", style: { writingMode: "sideways-lr" }, children: "Siren" }), openFeedbackCount > 0 && (jsx("span", { className: "absolute -top-3 -left-1 min-w-[24px] h-[24px] px-1 bg-[#E6B6CF] text-black rounded-full text-xs font-semibold flex items-center justify-center", children: openFeedbackCount }))] }), jsxs("div", { className: "bg-white w-[500px] h-[750px] rounded-l-xl shadow-2xl overflow-hidden flex flex-col text-black", children: [jsxs("div", { className: "flex items-center justify-between px-5 py-4 text-black", style: { backgroundColor: "var(--markup-primary)" }, children: [jsx("h2", { className: "text-base font-semibold m-0", children: ((_a = config.labels) === null || _a === void 0 ? void 0 : _a.feedbackTitle) || "Feedback" }), isAuthenticated && currentUser && (jsx("div", { onClick: handleLogout, className: "text-xs py-2 px-4 bg-gray-50 border border-gray-200 text-black rounded-xl hover:bg-gray-200 cursor-pointer transition-all flex items-center gap-2", children: jsx("h2", { children: "Sign Out" }) }))] }), !isAuthenticated ? (jsx("div", { className: "flex-1 overflow-y-auto", children: jsx(AuthForm, { onSuccess: handleAuthSuccess }) })) : selectedFeedback ? (jsx(FeedbackDetail, { feedback: selectedFeedback, onBack: handleBackFromDetail, onDelete: handleDeleteFeedback, onUpdate: handleUpdateFeedback })) : (jsxs(Fragment, { children: [jsxs("div", { className: "flex border-b border-gray-200 px-5", children: [jsxs("div", { className: cn("flex-1 py-3 px-4 border border-b-0 border-gray-200 rounded-t-xl hover:bg-gray-100 cursor-pointer text-sm font-medium transition-all flex items-center justify-center gap-1", activeTab === "create"
40786
+ }, "data-markup-widget": true, children: [jsxs("div", { onClick: handleToggle, className: "relative bg-white h-fit flex items-center gap-2 px-2 pr-5 text-black border-none rounded-l-2xl cursor-pointer text-sm font-semibold transition-all py-5 shadow-lg", children: [isOpen && jsx(CloseIcon, { className: "w-5 h-5" }), jsx("span", { className: "text-black", style: { writingMode: "sideways-lr" }, children: "Siren" }), openFeedbackCount > 0 && (jsx("span", { className: "absolute -top-3 -left-1 min-w-[24px] h-[24px] px-1 bg-[#E6B6CF] text-black rounded-full text-xs font-semibold flex items-center justify-center", children: openFeedbackCount }))] }), jsxs("div", { className: "bg-white w-[500px] h-[750px] rounded-l-xl shadow-2xl overflow-hidden flex flex-col text-black", children: [jsxs("div", { className: "flex items-center justify-between px-5 py-4 text-black", style: { backgroundColor: "var(--markup-primary)" }, children: [jsx("h2", { className: "text-base font-semibold m-0", children: ((_a = config.labels) === null || _a === void 0 ? void 0 : _a.feedbackTitle) || "Feedback" }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("div", { onClick: (e) => {
40787
+ e.stopPropagation();
40788
+ setShowPins(!showPins);
40789
+ }, className: "p-2 text-xs font-medium bg-gray-50 border border-gray-200 text-black rounded-lg hover:bg-gray-100 cursor-pointer transition-all flex items-center gap-1.5", title: showPins ? "Hide pins" : "Show pins", children: [jsxs("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z" })] }), showPins ? 'Hide' : 'Show'] }), isAuthenticated && currentUser && (jsx("div", { onClick: handleLogout, className: "text-xs py-2 px-2 bg-gray-50 border border-gray-200 text-gray-900 rounded-lg hover:bg-gray-200 cursor-pointer transition-all flex items-center gap-2", children: jsx("h2", { children: "Sign Out" }) }))] })] }), !isAuthenticated ? (jsx("div", { className: "flex-1 overflow-y-auto", children: jsx(AuthForm, { onSuccess: handleAuthSuccess }) })) : selectedFeedback ? (jsx(FeedbackDetail, { feedback: selectedFeedback, onBack: handleBackFromDetail, onDelete: handleDeleteFeedback, onUpdate: handleUpdateFeedback })) : (jsxs(Fragment, { children: [jsxs("div", { className: "flex border-b border-gray-200 px-5", children: [jsxs("div", { className: cn("flex-1 py-3 px-4 border border-b-0 border-gray-200 rounded-t-xl hover:bg-gray-100 cursor-pointer text-sm font-medium transition-all flex items-center justify-center gap-1", activeTab === "create"
40622
40790
  ? "bg-gray-200"
40623
40791
  : "border-white"), onClick: () => setActiveTab("create"), children: [jsx(PlusIcon, { className: "w-3.5 h-3.5" }), "New"] }), jsxs("div", { className: cn("flex-1 py-3 px-4 border border-b-0 border-gray-200 rounded-t-xl hover:bg-gray-100 cursor-pointer text-sm font-medium transition-all flex items-center justify-center gap-1", activeTab === "list"
40624
40792
  ? "bg-gray-200"