@sirendesign/markup 1.0.9 → 1.0.11

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/index.esm.js CHANGED
@@ -846,6 +846,7 @@ const initialState = {
846
846
  config: defaultConfig,
847
847
  currentUser: null,
848
848
  isAuthenticated: false,
849
+ viewportMode: null,
849
850
  };
850
851
  const useMarkupStore = create()(persist((set, get) => ({
851
852
  ...initialState,
@@ -853,6 +854,7 @@ const useMarkupStore = create()(persist((set, get) => ({
853
854
  setIsCapturing: (isCapturing) => set({ isCapturing }),
854
855
  setIsAnnotating: (isAnnotating) => set({ isAnnotating }),
855
856
  setCurrentScreenshot: (screenshot) => set({ currentScreenshot: screenshot }),
857
+ setAnnotations: (annotations) => set({ annotations }),
856
858
  addAnnotation: (annotation) => set((state) => ({
857
859
  annotations: [...state.annotations, annotation],
858
860
  })),
@@ -891,6 +893,7 @@ const useMarkupStore = create()(persist((set, get) => ({
891
893
  console.log('Store: Setting authenticated:', isAuthenticated);
892
894
  set({ isAuthenticated });
893
895
  },
896
+ setViewportMode: (mode) => set({ viewportMode: mode }),
894
897
  addComment: (feedbackId, comment) => set((state) => ({
895
898
  feedbackItems: state.feedbackItems.map((f) => f.id === feedbackId
896
899
  ? { ...f, comments: [...f.comments, comment], updatedAt: new Date().toISOString() }
@@ -8736,7 +8739,65 @@ var parseBackgroundColor = function (context, element, backgroundColorOverride)
8736
8739
  : defaultBackgroundColor;
8737
8740
  };
8738
8741
 
8739
- async function captureScreenshot() {
8742
+ const identifyComponent = (element) => {
8743
+ var _a, _b;
8744
+ if (!element)
8745
+ return undefined;
8746
+ let current = element;
8747
+ const depth = 5; // Search up 5 levels
8748
+ for (let i = 0; i < depth; i++) {
8749
+ if (!current)
8750
+ break;
8751
+ // 1. Check for data-component attribute (common pattern)
8752
+ if (current.getAttribute('data-component')) {
8753
+ return current.getAttribute('data-component') || undefined;
8754
+ }
8755
+ // 2. Check for data-testid
8756
+ if (current.getAttribute('data-testid')) {
8757
+ return current.getAttribute('data-testid') || undefined;
8758
+ }
8759
+ // 3. React DevTools internals (brittle, but sometimes works)
8760
+ // @ts-ignore
8761
+ const key = Object.keys(current).find(k => k.startsWith('__reactFiber$'));
8762
+ if (key) {
8763
+ // @ts-ignore
8764
+ const fiber = current[key];
8765
+ if ((_a = fiber === null || fiber === void 0 ? void 0 : fiber.type) === null || _a === void 0 ? void 0 : _a.displayName) {
8766
+ return fiber.type.displayName;
8767
+ }
8768
+ if ((_b = fiber === null || fiber === void 0 ? void 0 : fiber.type) === null || _b === void 0 ? void 0 : _b.name) {
8769
+ return fiber.type.name;
8770
+ }
8771
+ }
8772
+ // 4. ID or specific classes
8773
+ if (current.id)
8774
+ return `#${current.id}`;
8775
+ if (current.parentElement) {
8776
+ current = current.parentElement;
8777
+ }
8778
+ else {
8779
+ break;
8780
+ }
8781
+ }
8782
+ return undefined;
8783
+ };
8784
+ const getElementInfo = (element) => {
8785
+ if (!element)
8786
+ return undefined;
8787
+ const tagName = element.tagName.toLowerCase();
8788
+ const classes = element.className && typeof element.className === 'string'
8789
+ ? element.className.split(' ').filter(c => c.trim()).slice(0, 3).join(' ')
8790
+ : '';
8791
+ let info = `<${tagName}`;
8792
+ if (element.id)
8793
+ info += ` id="${element.id}"`;
8794
+ if (classes)
8795
+ info += ` class="${classes}"`;
8796
+ info += '>';
8797
+ return info;
8798
+ };
8799
+
8800
+ async function captureScreenshot(clickElement) {
8740
8801
  try {
8741
8802
  // Hide the markup widget before capturing
8742
8803
  const widget = document.querySelector('[data-markup-widget]');
@@ -8749,6 +8810,12 @@ async function captureScreenshot() {
8749
8810
  scale: window.devicePixelRatio,
8750
8811
  logging: false,
8751
8812
  backgroundColor: null,
8813
+ width: window.innerWidth,
8814
+ height: window.innerHeight,
8815
+ x: window.scrollX,
8816
+ y: window.scrollY,
8817
+ windowWidth: window.innerWidth,
8818
+ windowHeight: window.innerHeight,
8752
8819
  ignoreElements: (element) => {
8753
8820
  return element.hasAttribute('data-markup-widget');
8754
8821
  },
@@ -8764,7 +8831,20 @@ async function captureScreenshot() {
8764
8831
  throw error;
8765
8832
  }
8766
8833
  }
8767
- function getPageMetadata() {
8834
+ const detectDeviceType = () => {
8835
+ const width = window.innerWidth;
8836
+ const userAgent = navigator.userAgent.toLowerCase();
8837
+ // Check user agent for mobile/tablet indicators
8838
+ const isMobileUA = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/i.test(userAgent);
8839
+ const isTabletUA = /tablet|ipad|playbook|silk|kindle/i.test(userAgent);
8840
+ // Prioritize user agent, then fall back to screen width
8841
+ if (isMobileUA && width < 768)
8842
+ return 'mobile';
8843
+ if (isTabletUA || (width >= 768 && width < 1024))
8844
+ return 'tablet';
8845
+ return 'desktop';
8846
+ };
8847
+ function getPageMetadata(clickElement) {
8768
8848
  return {
8769
8849
  url: window.location.href,
8770
8850
  title: document.title,
@@ -8773,6 +8853,9 @@ function getPageMetadata() {
8773
8853
  devicePixelRatio: window.devicePixelRatio,
8774
8854
  userAgent: navigator.userAgent,
8775
8855
  timestamp: new Date().toISOString(),
8856
+ deviceType: detectDeviceType(),
8857
+ componentName: clickElement ? identifyComponent(clickElement) : undefined,
8858
+ elementInfo: clickElement ? getElementInfo(clickElement) : undefined,
8776
8859
  };
8777
8860
  }
8778
8861
  function getElementSelector(element) {
@@ -11442,6 +11525,7 @@ const UndoIcon = ({ className, ...props }) => (jsxs("svg", { className: classNam
11442
11525
  const CheckIcon = ({ className, ...props }) => (jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
11443
11526
  const TrashIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("polyline", { points: "3 6 5 6 21 6" }), jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })] }));
11444
11527
  const EditIcon = ({ 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: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }), jsx("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })] }));
11528
+ const CopyIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }));
11445
11529
  const InboxIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }), jsx("path", { d: "M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] }));
11446
11530
  const SendIcon = ({ 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: "22", y1: "2", x2: "11", y2: "13" }), jsx("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })] }));
11447
11531
  const PlusIcon = ({ 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: "12", y1: "5", x2: "12", y2: "19" }), jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] }));
@@ -11454,6 +11538,9 @@ const UserIcon = ({ className, ...props }) => (jsxs("svg", { className: classNam
11454
11538
  const ZoomInIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("circle", { cx: "11", cy: "11", r: "8" }), jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" }), jsx("line", { x1: "11", y1: "8", x2: "11", y2: "14" }), jsx("line", { x1: "8", y1: "11", x2: "14", y2: "11" })] }));
11455
11539
  const CalendarIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2", ry: "2" }), jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }), jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }), jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" })] }));
11456
11540
  const ChevronRightIcon = ({ className, ...props }) => (jsx("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: jsx("polyline", { points: "9 18 15 12 9 6" }) }));
11541
+ const DesktopIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }), jsx("line", { x1: "8", y1: "21", x2: "16", y2: "21" }), jsx("line", { x1: "12", y1: "17", x2: "12", y2: "21" })] }));
11542
+ const TabletIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }), jsx("line", { x1: "12", y1: "18", x2: "12.01", y2: "18" })] }));
11543
+ const MobileIcon = ({ className, ...props }) => (jsxs("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", ...props, children: [jsx("rect", { x: "7", y: "2", width: "10", height: "20", rx: "2", ry: "2" }), jsx("line", { x1: "12", y1: "18", x2: "12.01", y2: "18" })] }));
11457
11544
 
11458
11545
  const tools = [
11459
11546
  { id: 'pen', icon: PencilIcon, label: 'Draw' },
@@ -11770,22 +11857,43 @@ const AnnotationOverlay = ({ screenshot, onComplete, onCancel, }) => {
11770
11857
  : 'Click and drag to annotate • Use tools above to switch modes' })] }), document.body);
11771
11858
  };
11772
11859
 
11773
- const FeedbackForm = ({ onSubmit, onCancel, }) => {
11860
+ const FeedbackForm = ({ onSubmit, onCancel, initialData, getSessionEvents, lastClickedElement, lastClickedText, }) => {
11774
11861
  var _a, _b;
11775
- const { config, currentScreenshot, setCurrentScreenshot, annotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, } = useMarkupStore();
11776
- const [title, setTitle] = useState("");
11777
- const [description, setDescription] = useState("");
11778
- const [priority, setPriority] = useState("medium");
11779
- const [feedbackType, setFeedbackType] = useState("general");
11780
- const [currentCopy, setCurrentCopy] = useState("");
11781
- const [newCopy, setNewCopy] = useState("");
11782
- const [selectedTags, setSelectedTags] = useState([]);
11862
+ const { config, currentScreenshot, setCurrentScreenshot, annotations, setAnnotations, clearAnnotations, setIsCapturing, isCapturing, currentUser, } = useMarkupStore();
11863
+ const [title, setTitle] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.title) || "");
11864
+ const [description, setDescription] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.description) || "");
11865
+ const [priority, setPriority] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.priority) || "medium");
11866
+ const [feedbackType, setFeedbackType] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.type) || "general");
11867
+ const [currentCopy, setCurrentCopy] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.currentCopy) || "");
11868
+ const [newCopy, setNewCopy] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.newCopy) || "");
11869
+ const [designMockup, setDesignMockup] = useState(initialData === null || initialData === void 0 ? void 0 : initialData.designMockup);
11870
+ const [selectedTags, setSelectedTags] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.tags) || []);
11783
11871
  const [isAnnotating, setIsAnnotating] = useState(false);
11784
11872
  const [isSubmitting, setIsSubmitting] = useState(false);
11785
- const [annotatedScreenshot, setAnnotatedScreenshot] = useState(null);
11786
- // Auto-capture selected text when component mounts
11873
+ const [annotatedScreenshot, setAnnotatedScreenshot] = useState((initialData === null || initialData === void 0 ? void 0 : initialData.screenshot) || null);
11874
+ // Screen recording states
11875
+ const [isRecording, setIsRecording] = useState(false);
11876
+ const [recordedVideo, setRecordedVideo] = useState(initialData === null || initialData === void 0 ? void 0 : initialData.screenRecording);
11877
+ const [mediaRecorder, setMediaRecorder] = useState(null);
11878
+ const [recordingStream, setRecordingStream] = useState(null);
11879
+ // Load initial annotations into store if editing
11880
+ useEffect(() => {
11881
+ if (initialData === null || initialData === void 0 ? void 0 : initialData.annotations) {
11882
+ setAnnotations(initialData.annotations);
11883
+ }
11884
+ }, [initialData, setAnnotations]);
11885
+ // Auto-capture selected text when component mounts (only if creating new)
11787
11886
  useEffect(() => {
11788
11887
  var _a;
11888
+ if (initialData)
11889
+ return;
11890
+ // First check if user clicked on text element
11891
+ if (lastClickedText && lastClickedText.length > 0) {
11892
+ setCurrentCopy(lastClickedText);
11893
+ setFeedbackType("copy-amendment");
11894
+ return;
11895
+ }
11896
+ // Fallback to selected text
11789
11897
  const selectedText = (_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.toString().trim();
11790
11898
  if (selectedText &&
11791
11899
  selectedText.length > 0 &&
@@ -11793,7 +11901,7 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11793
11901
  setCurrentCopy(selectedText);
11794
11902
  setFeedbackType("copy-amendment");
11795
11903
  }
11796
- }, []);
11904
+ }, [initialData, lastClickedText]);
11797
11905
  const handleCaptureScreenshot = async () => {
11798
11906
  try {
11799
11907
  setIsCapturing(true);
@@ -11808,6 +11916,57 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11808
11916
  setIsCapturing(false);
11809
11917
  }
11810
11918
  };
11919
+ const handleStartRecording = async () => {
11920
+ try {
11921
+ const stream = await navigator.mediaDevices.getDisplayMedia({
11922
+ video: { mediaSource: 'screen' },
11923
+ audio: false,
11924
+ });
11925
+ setRecordingStream(stream);
11926
+ const recorder = new MediaRecorder(stream, {
11927
+ mimeType: 'video/webm;codecs=vp8',
11928
+ });
11929
+ const chunks = [];
11930
+ recorder.ondataavailable = (e) => {
11931
+ if (e.data.size > 0) {
11932
+ chunks.push(e.data);
11933
+ }
11934
+ };
11935
+ recorder.onstop = () => {
11936
+ const blob = new Blob(chunks, { type: 'video/webm' });
11937
+ // Convert blob to base64 data URL for storage
11938
+ const reader = new FileReader();
11939
+ reader.onloadend = () => {
11940
+ const base64data = reader.result;
11941
+ setRecordedVideo(base64data);
11942
+ };
11943
+ reader.readAsDataURL(blob);
11944
+ // Stop all tracks
11945
+ stream.getTracks().forEach(track => track.stop());
11946
+ setRecordingStream(null);
11947
+ };
11948
+ recorder.start();
11949
+ setMediaRecorder(recorder);
11950
+ setIsRecording(true);
11951
+ }
11952
+ catch (error) {
11953
+ console.error('Failed to start recording:', error);
11954
+ alert('Screen recording is not supported or permission was denied.');
11955
+ }
11956
+ };
11957
+ const handleStopRecording = () => {
11958
+ if (mediaRecorder && mediaRecorder.state !== 'inactive') {
11959
+ mediaRecorder.stop();
11960
+ setIsRecording(false);
11961
+ }
11962
+ };
11963
+ const handleDeleteRecording = () => {
11964
+ setRecordedVideo(undefined);
11965
+ if (recordingStream) {
11966
+ recordingStream.getTracks().forEach(track => track.stop());
11967
+ setRecordingStream(null);
11968
+ }
11969
+ };
11811
11970
  const handleAnnotationComplete = (newAnnotations, screenshot) => {
11812
11971
  setAnnotatedScreenshot(screenshot);
11813
11972
  setIsAnnotating(false);
@@ -11830,6 +11989,23 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11830
11989
  const toggleTag = (tag) => {
11831
11990
  setSelectedTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
11832
11991
  };
11992
+ const priorityColors = {
11993
+ low: "bg-brand-green/30 text-gray-700",
11994
+ medium: "bg-brand-blue/50 text-gray-800",
11995
+ high: "bg-brand-pink/30 text-gray-800",
11996
+ critical: "bg-brand-pink text-gray-800",
11997
+ };
11998
+ const handleMockupUpload = (e) => {
11999
+ var _a;
12000
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
12001
+ if (file) {
12002
+ const reader = new FileReader();
12003
+ reader.onloadend = () => {
12004
+ setDesignMockup(reader.result);
12005
+ };
12006
+ reader.readAsDataURL(file);
12007
+ }
12008
+ };
11833
12009
  const handleSubmit = async (e) => {
11834
12010
  var _a, _b, _c, _d;
11835
12011
  e.preventDefault();
@@ -11839,7 +12015,7 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11839
12015
  return;
11840
12016
  setIsSubmitting(true);
11841
12017
  const feedback = {
11842
- id: generateId(),
12018
+ id: (initialData === null || initialData === void 0 ? void 0 : initialData.id) || generateId(),
11843
12019
  type: feedbackType,
11844
12020
  title: title.trim() || (feedbackType === "copy-amendment" ? "Copy Amendment" : ""),
11845
12021
  description: description.trim(),
@@ -11849,19 +12025,22 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11849
12025
  newCopy: feedbackType === "copy-amendment" ? newCopy.trim() : undefined,
11850
12026
  screenshot: annotatedScreenshot || currentScreenshot || undefined,
11851
12027
  annotations: annotations,
11852
- status: "open",
12028
+ status: (initialData === null || initialData === void 0 ? void 0 : initialData.status) || "open",
11853
12029
  priority,
11854
- pageMetadata: getPageMetadata(),
11855
- createdAt: new Date().toISOString(),
12030
+ pageMetadata: (initialData === null || initialData === void 0 ? void 0 : initialData.pageMetadata) || getPageMetadata(lastClickedElement || undefined),
12031
+ createdAt: (initialData === null || initialData === void 0 ? void 0 : initialData.createdAt) || new Date().toISOString(),
11856
12032
  updatedAt: new Date().toISOString(),
11857
- createdBy: currentUser || {
12033
+ createdBy: (initialData === null || initialData === void 0 ? void 0 : initialData.createdBy) || currentUser || {
11858
12034
  id: ((_a = config.user) === null || _a === void 0 ? void 0 : _a.id) || "anonymous",
11859
12035
  name: ((_b = config.user) === null || _b === void 0 ? void 0 : _b.name) || "Anonymous",
11860
12036
  email: ((_c = config.user) === null || _c === void 0 ? void 0 : _c.email) || "",
11861
12037
  role: ((_d = config.user) === null || _d === void 0 ? void 0 : _d.role) || "default",
11862
12038
  },
11863
- comments: [],
12039
+ comments: (initialData === null || initialData === void 0 ? void 0 : initialData.comments) || [],
11864
12040
  tags: selectedTags,
12041
+ sessionEvents: getSessionEvents ? getSessionEvents() : undefined,
12042
+ designMockup,
12043
+ screenRecording: recordedVideo,
11865
12044
  };
11866
12045
  try {
11867
12046
  if (config.onSubmit) {
@@ -11891,7 +12070,9 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11891
12070
  { id: "copy-amendment", label: "Copy Change" },
11892
12071
  ].map((type) => (jsx("div", { onClick: () => setFeedbackType(type.id), className: cn("flex-1 py-2 px-3 rounded-xl text-xs font-medium cursor-pointer transition-all text-center", feedbackType === type.id
11893
12072
  ? "bg-[#C2D1D9] text-black shadow-md shadow-[#C2D1D9]/30"
11894
- : "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), className: "w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-sm min-h-[100px] resize-y focus:outline-none focus:bg-white focus:border-[#C2D1D9] focus:shadow-lg focus:shadow-[#C2D1D9]/10 transition-all" })] })), 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"] })] })] })) : (jsx("div", { onClick: handleCaptureScreenshot, className: "w-full 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 mb-5 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" }), " Capture Screenshot"] })) })), 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)
12073
+ : "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
12074
+ ? cn(priorityColors[p], "border-transparent shadow-sm")
12075
+ : "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)
11895
12076
  ? "bg-[#C2D1D9] text-white shadow-md shadow-[#C2D1D9]/30"
11896
12077
  : "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) => {
11897
12078
  const isValid = feedbackType === "copy-amendment" ? newCopy.trim() : title.trim();
@@ -11910,7 +12091,7 @@ const FeedbackForm = ({ onSubmit, onCancel, }) => {
11910
12091
  backgroundColor: (feedbackType === "copy-amendment" ? !newCopy.trim() : !title.trim()) || isSubmitting
11911
12092
  ? "#9ca3af"
11912
12093
  : "green-500",
11913
- }, children: isSubmitting ? (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) || "Submit Feedback") })] })] }));
12094
+ }, children: isSubmitting ? (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")) })] })] }));
11914
12095
  };
11915
12096
 
11916
12097
  const statusOptions$1 = [
@@ -11921,16 +12102,16 @@ const statusOptions$1 = [
11921
12102
  { value: "closed", label: "Closed" },
11922
12103
  ];
11923
12104
  const priorityColors$1 = {
11924
- low: "bg-gray-100 text-gray-700",
11925
- medium: "bg-yellow-100 text-yellow-800",
11926
- high: "bg-orange-100 text-orange-800",
11927
- critical: "bg-red-100 text-red-800",
12105
+ low: "bg-brand-green/30 text-gray-700",
12106
+ medium: "bg-brand-blue/50 text-gray-800",
12107
+ high: "bg-brand-pink/30 text-gray-800",
12108
+ critical: "bg-brand-pink text-gray-800",
11928
12109
  };
11929
12110
  const statusColors = {
11930
- open: "bg-blue-100 text-blue-800",
11931
- "in-progress": "bg-purple-100 text-purple-800",
11932
- resolved: "bg-green-100 text-green-800",
11933
- closed: "bg-gray-100 text-gray-600",
12111
+ open: "bg-brand-blue/50 text-gray-800",
12112
+ "in-progress": "bg-yellow-100 text-gray-800",
12113
+ resolved: "bg-brand-green/50 text-gray-900",
12114
+ closed: "bg-brand-pink/30 text-gray-800",
11934
12115
  };
11935
12116
  const FeedbackList = ({ feedbackItems, onSelectFeedback, }) => {
11936
12117
  const { config } = useMarkupStore();
@@ -11968,7 +12149,10 @@ const FeedbackList = ({ feedbackItems, onSelectFeedback, }) => {
11968
12149
  }, [feedbackItems, statusFilter, sortBy]);
11969
12150
  return (jsxs("div", { className: "flex flex-col h-full", children: [jsxs("div", { className: "flex gap-2 pb-4 mb-4", children: [jsxs("div", { className: "relative flex-1", children: [jsx(FilterIcon, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" }), jsx("select", { value: statusFilter, onChange: (e) => setStatusFilter(e.target.value), className: "w-full pl-9 pr-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-medium appearance-none focus:outline-none focus:bg-white focus:border-[#C2D1D9] focus:shadow-lg focus:shadow-[#C2D1D9]/10 transition-all", children: statusOptions$1.map((option) => (jsx("option", { value: option.value, children: option.label }, option.value))) })] }), jsxs("div", { className: "relative flex-1", children: [jsx(CalendarIcon, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" }), jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "w-full pl-9 pr-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-xs font-medium appearance-none focus:outline-none focus:bg-white focus:border-[#C2D1D9] focus:shadow-lg focus:shadow-[#C2D1D9]/10 transition-all", children: [jsx("option", { value: "newest", children: "Newest First" }), jsx("option", { value: "oldest", children: "Oldest First" }), jsx("option", { value: "priority", children: "Priority" })] })] })] }), jsx("div", { className: "flex-1 overflow-y-auto -mx-1 px-1", children: filteredAndSortedItems.length === 0 ? (jsxs("div", { className: "flex flex-col items-center justify-center py-10 text-gray-400", children: [jsx(InboxIcon, { className: "w-12 h-12 mb-3 opacity-50" }), jsx("p", { className: "text-sm font-medium text-gray-500", children: "No feedback items" }), jsx("p", { className: "text-xs text-gray-400 mt-1", children: statusFilter !== "all"
11970
12151
  ? "Try changing the filter"
11971
- : "Be the first to add feedback!" })] })) : (jsx("div", { className: "flex flex-col gap-3", children: filteredAndSortedItems.map((item) => (jsxs("div", { onClick: () => onSelectFeedback(item), className: "w-full p-4 bg-white rounded-xl border border-gray-200 cursor-pointer text-left transition-all hover:shadow-lg hover:shadow-gray-200 hover:border-gray-300", children: [jsxs("div", { className: "flex items-start justify-between gap-2 mb-2", children: [jsx("h4", { className: "text-sm font-semibold text-gray-900 m-0 flex-1 line-clamp-2", children: item.title }), jsx(ChevronRightIcon, { className: "w-4 h-4 text-gray-400 flex-shrink-0 mt-0.5" })] }), item.description && (jsx("p", { className: "text-xs text-gray-600 m-0 mb-3 line-clamp-2", children: item.description })), jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [jsx("span", { className: cn("px-2.5 py-1 rounded-lg text-[10px] font-semibold uppercase tracking-wide", statusColors[item.status]), children: item.status.replace("-", " ") }), jsx("span", { className: cn("px-2.5 py-1 rounded-lg text-[10px] font-semibold uppercase tracking-wide", priorityColors$1[item.priority]), children: item.priority }), item.createdBy && (jsxs("span", { className: "text-[10px] text-gray-600 font-medium", children: ["by ", item.createdBy.name] })), jsx("span", { className: "text-[10px] text-gray-500 ml-auto font-medium", children: formatDate(item.createdAt) })] }), item.tags && item.tags.length > 0 && (jsxs("div", { className: "flex flex-wrap gap-1.5 mt-3", children: [item.tags.slice(0, 3).map((tag) => (jsx("span", { className: "px-2 py-1 bg-gray-100 text-gray-700 rounded-md text-[10px] font-medium", children: tag }, tag))), item.tags.length > 3 && (jsxs("span", { className: "px-2 py-1 text-gray-500 text-[10px] font-medium", children: ["+", item.tags.length - 3, " more"] }))] }))] }, item.id))) })) }), feedbackItems.length > 0 && (jsxs("div", { className: "pt-3 mt-3 border-t border-gray-200 text-xs text-gray-400 text-center", children: ["Showing ", filteredAndSortedItems.length, " of", " ", feedbackItems.length, " items"] }))] }));
12152
+ : "Be the first to add feedback!" })] })) : (jsx("div", { className: "flex flex-col gap-3", children: filteredAndSortedItems.map((item) => {
12153
+ var _a;
12154
+ return (jsxs("div", { onClick: () => onSelectFeedback(item), className: "w-full p-4 bg-white rounded-xl border border-gray-200 cursor-pointer text-left transition-all hover:shadow-lg hover:shadow-gray-200 hover:border-gray-300", children: [jsxs("div", { className: "flex items-start justify-between gap-2 mb-2", children: [jsxs("div", { className: "flex items-center gap-2 flex-1", children: [jsx("h4", { className: "text-sm font-semibold text-gray-900 m-0 flex-1 line-clamp-2", children: item.title }), ((_a = item.pageMetadata) === null || _a === void 0 ? void 0 : _a.deviceType) && (jsxs("div", { className: "flex-shrink-0", children: [item.pageMetadata.deviceType === 'desktop' && (jsx(DesktopIcon, { className: "w-4 h-4 text-gray-400", title: "Desktop" })), item.pageMetadata.deviceType === 'tablet' && (jsx(TabletIcon, { className: "w-4 h-4 text-gray-400", title: "Tablet" })), item.pageMetadata.deviceType === 'mobile' && (jsx(MobileIcon, { className: "w-4 h-4 text-gray-400", title: "Mobile" }))] }))] }), jsx(ChevronRightIcon, { className: "w-4 h-4 text-gray-400 flex-shrink-0 mt-0.5" })] }), item.description && (jsx("p", { className: "text-xs text-gray-600 m-0 mb-3 line-clamp-2", children: item.description })), jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [jsx("span", { className: cn("px-2.5 py-1 rounded-lg text-[10px] font-semibold uppercase tracking-wide", statusColors[item.status]), children: item.status.replace("-", " ") }), jsx("span", { className: cn("px-2.5 py-1 rounded-lg text-[10px] font-semibold uppercase tracking-wide", priorityColors$1[item.priority]), children: item.priority }), item.createdBy && (jsxs("span", { className: "text-[10px] text-gray-600 font-medium", children: ["by ", item.createdBy.name] })), jsx("span", { className: "text-[10px] text-gray-500 ml-auto font-medium", children: formatDate(item.createdAt) })] }), item.tags && item.tags.length > 0 && (jsxs("div", { className: "flex flex-wrap gap-1.5 mt-3", children: [item.tags.slice(0, 3).map((tag) => (jsx("span", { className: "px-2 py-1 bg-gray-100 text-gray-700 rounded-md text-[10px] font-medium", children: tag }, tag))), item.tags.length > 3 && (jsxs("span", { className: "px-2 py-1 text-gray-500 text-[10px] font-medium", children: ["+", item.tags.length - 3, " more"] }))] }))] }, item.id));
12155
+ }) })) }), feedbackItems.length > 0 && (jsxs("div", { className: "pt-3 mt-3 border-t border-gray-200 text-xs text-gray-400 text-center", children: ["Showing ", filteredAndSortedItems.length, " of", " ", feedbackItems.length, " items"] }))] }));
11972
12156
  };
11973
12157
 
11974
12158
  const getDefaultsFromPostinstall = () => (undefined);
@@ -37132,6 +37316,7 @@ const subscribeFeedback = (projectId, callback) => {
37132
37316
  devicePixelRatio: 1,
37133
37317
  userAgent: '',
37134
37318
  timestamp: new Date().toISOString(),
37319
+ deviceType: 'desktop',
37135
37320
  },
37136
37321
  createdAt: data.createdAt.toDate().toISOString(),
37137
37322
  updatedAt: data.createdAt.toDate().toISOString(),
@@ -37139,9 +37324,13 @@ const subscribeFeedback = (projectId, callback) => {
37139
37324
  id: data.userId,
37140
37325
  name: 'User',
37141
37326
  email: '',
37142
- role: 'client',
37327
+ role: 'default',
37143
37328
  },
37144
37329
  tags: data.tags || [],
37330
+ sessionEvents: data.sessionEvents,
37331
+ componentName: data.componentName,
37332
+ designMockup: data.designMockup,
37333
+ screenRecording: data.screenRecording,
37145
37334
  };
37146
37335
  });
37147
37336
  callback(feedbackItems);
@@ -37201,30 +37390,114 @@ const canDeleteFeedback = (currentUser, feedbackCreatorId) => {
37201
37390
  }
37202
37391
  return false;
37203
37392
  };
37393
+ /**
37394
+ * Check if a user can edit/amend feedback
37395
+ * Only the creator, developers, or admins can edit
37396
+ */
37397
+ const canEditFeedback = (currentUser, feedbackCreatorId) => {
37398
+ // Creator can edit their own feedback
37399
+ if (currentUser.id === feedbackCreatorId) {
37400
+ return true;
37401
+ }
37402
+ // Admins and developers can edit any feedback
37403
+ if (currentUser.role === 'admin' || currentUser.role === 'developer') {
37404
+ return true;
37405
+ }
37406
+ return false;
37407
+ };
37204
37408
 
37205
37409
  const statusOptions = [
37206
- { value: "open", label: "Open", color: "bg-blue-500" },
37207
- { value: "in-progress", label: "In Progress", color: "bg-purple-500" },
37208
- { value: "resolved", label: "Resolved", color: "bg-green-500" },
37209
- { value: "closed", label: "Closed", color: "bg-gray-500" },
37410
+ { value: "open", label: "Open", color: "bg-brand-blue text-gray-900" },
37411
+ {
37412
+ value: "in-progress",
37413
+ label: "In Progress",
37414
+ color: "bg-yellow-100 text-gray-900",
37415
+ },
37416
+ {
37417
+ value: "resolved",
37418
+ label: "Resolved",
37419
+ color: "bg-brand-green text-white",
37420
+ },
37421
+ {
37422
+ value: "closed",
37423
+ label: "Closed",
37424
+ color: "bg-brand-pink text-gray-900",
37425
+ },
37210
37426
  ];
37211
37427
  const priorityColors = {
37212
- low: "bg-gray-100 text-gray-700",
37213
- medium: "bg-yellow-100 text-yellow-800",
37214
- high: "bg-orange-100 text-orange-800",
37215
- critical: "bg-red-100 text-red-800",
37428
+ low: "bg-brand-green/30 text-gray-700",
37429
+ medium: "bg-brand-blue/50 text-gray-800",
37430
+ high: "bg-brand-pink/30 text-gray-800",
37431
+ critical: "bg-brand-pink text-gray-800",
37216
37432
  };
37217
37433
  const FeedbackDetail = ({ feedback: initialFeedback, onBack, onDelete, onUpdate, }) => {
37218
- var _a, _b;
37434
+ var _a, _b, _c;
37219
37435
  const { config, updateFeedback: updateFeedback$1, currentUser } = useMarkupStore();
37220
37436
  const [feedback, setFeedback] = useState(initialFeedback);
37221
37437
  const [newComment, setNewComment] = useState("");
37222
37438
  const [isAddingComment, setIsAddingComment] = useState(false);
37223
37439
  const [showFullScreenshot, setShowFullScreenshot] = useState(false);
37224
37440
  const [copied, setCopied] = useState(false);
37441
+ const [isEditing, setIsEditing] = useState(false);
37442
+ // Feature 2 & 5 states
37443
+ const [showReplay, setShowReplay] = useState(false);
37444
+ const [showOverlay, setShowOverlay] = useState(false);
37445
+ const [overlayOpacity, setOverlayOpacity] = useState(50);
37446
+ // Parse session actions
37447
+ const sessionActions = useMemo(() => {
37448
+ if (!feedback.sessionEvents)
37449
+ return null;
37450
+ try {
37451
+ return JSON.parse(feedback.sessionEvents);
37452
+ }
37453
+ catch (e) {
37454
+ console.error("Failed to parse session events", e);
37455
+ return null;
37456
+ }
37457
+ }, [feedback.sessionEvents]);
37458
+ // Format timestamp
37459
+ const formatTimestamp = (timestamp) => {
37460
+ const date = new Date(timestamp);
37461
+ return date.toLocaleTimeString('en-US', {
37462
+ hour: '2-digit',
37463
+ minute: '2-digit',
37464
+ second: '2-digit',
37465
+ hour12: false
37466
+ });
37467
+ };
37468
+ // Get relative time
37469
+ const getRelativeTime = (timestamp, baseTimestamp) => {
37470
+ const diff = Math.floor((timestamp - baseTimestamp) / 1000);
37471
+ if (diff === 0)
37472
+ return 'start';
37473
+ return `+${diff}s`;
37474
+ };
37225
37475
  const isReadOnly = ((_a = config.user) === null || _a === void 0 ? void 0 : _a.role) === "default" || config.allowStatusChange === false;
37226
- // Check if current user can delete this feedback
37227
- const canDelete = currentUser ? canDeleteFeedback(currentUser, feedback.createdBy.id) : false;
37476
+ // Check if current user can delete/edit this feedback
37477
+ const canDelete = currentUser
37478
+ ? canDeleteFeedback(currentUser, feedback.createdBy.id)
37479
+ : false;
37480
+ const canEdit = currentUser
37481
+ ? canEditFeedback(currentUser, feedback.createdBy.id)
37482
+ : false;
37483
+ const handleEditSubmit = async (updatedFeedback) => {
37484
+ setFeedback(updatedFeedback);
37485
+ updateFeedback$1(updatedFeedback);
37486
+ // Update in Firebase if initialized
37487
+ try {
37488
+ await updateFeedback(feedback.id, updatedFeedback);
37489
+ }
37490
+ catch (error) {
37491
+ console.warn("Failed to update feedback in Firebase:", error);
37492
+ }
37493
+ if (onUpdate) {
37494
+ onUpdate(updatedFeedback);
37495
+ }
37496
+ setIsEditing(false);
37497
+ };
37498
+ if (isEditing) {
37499
+ return (jsxs("div", { className: "flex flex-col h-full", children: [jsx("div", { className: "flex items-center justify-between p-5 pb-3 mb-3 border-b border-gray-200", children: jsxs("div", { onClick: () => setIsEditing(false), 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" }), "Cancel Editing"] }) }), jsx("div", { className: "flex-1 overflow-y-auto px-5", children: jsx(FeedbackForm, { initialData: feedback, onSubmit: handleEditSubmit, onCancel: () => setIsEditing(false) }) })] }));
37500
+ }
37228
37501
  const handleCopy = async () => {
37229
37502
  const feedbackText = `
37230
37503
  📝 ${feedback.title}
@@ -37268,7 +37541,7 @@ ${feedback.comments && feedback.comments.length > 0
37268
37541
  await updateFeedback(feedback.id, { status: newStatus });
37269
37542
  }
37270
37543
  catch (error) {
37271
- console.warn('Failed to update status in Firebase:', error);
37544
+ console.warn("Failed to update status in Firebase:", error);
37272
37545
  }
37273
37546
  if (config.onStatusChange) {
37274
37547
  await config.onStatusChange(feedback.id, newStatus);
@@ -37304,11 +37577,11 @@ ${feedback.comments && feedback.comments.length > 0
37304
37577
  // Update in Firebase if initialized
37305
37578
  try {
37306
37579
  await updateFeedback(feedback.id, {
37307
- comments: updatedFeedback.comments
37580
+ comments: updatedFeedback.comments,
37308
37581
  });
37309
37582
  }
37310
37583
  catch (error) {
37311
- console.warn('Failed to add comment in Firebase:', error);
37584
+ console.warn("Failed to add comment in Firebase:", error);
37312
37585
  }
37313
37586
  if (config.onCommentAdd) {
37314
37587
  await config.onCommentAdd(feedback.id, comment);
@@ -37327,7 +37600,7 @@ ${feedback.comments && feedback.comments.length > 0
37327
37600
  await deleteFeedback(feedback.id);
37328
37601
  }
37329
37602
  catch (error) {
37330
- console.warn('Failed to delete from Firebase:', error);
37603
+ console.warn("Failed to delete from Firebase:", error);
37331
37604
  }
37332
37605
  if (config.onDelete) {
37333
37606
  await config.onDelete(feedback.id);
@@ -37336,22 +37609,28 @@ ${feedback.comments && feedback.comments.length > 0
37336
37609
  onDelete(feedback.id);
37337
37610
  }
37338
37611
  };
37339
- return (jsxs("div", { className: "flex flex-col h-full px-5", 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-indigo-600 bg-transparent border-none cursor-pointer hover:text-indigo-700 transition-colors", children: copied ? (jsxs(Fragment, { children: [jsx(CheckIcon, { className: "w-4 h-4" }), "Copied!"] })) : (jsxs(Fragment, { children: [jsxs("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }), "Copy"] })) }), canDelete && onDelete && (jsxs("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" }), "Delete"] }))] })] }), jsxs("div", { className: "flex-1 overflow-y-auto pr-1", children: [jsxs("div", { className: "mb-4", children: [jsx("h3", { className: "text-base font-semibold text-gray-900 m-0 mb-2", children: feedback.title }), 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"
37340
- ? "bg-purple-100 text-purple-800"
37612
+ return (jsxs("div", { className: "flex flex-col h-full px-5", 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 overflow-y-auto 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 === 'desktop' && (jsx(DesktopIcon, { className: "w-5 h-5 text-gray-500", title: "Desktop" })), feedback.pageMetadata.deviceType === 'tablet' && (jsx(TabletIcon, { className: "w-5 h-5 text-gray-500", title: "Tablet" })), feedback.pageMetadata.deviceType === '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"
37613
+ ? "bg-brand-blue/50 text-gray-800"
37341
37614
  : feedback.type === "bug"
37342
- ? "bg-red-100 text-red-800"
37615
+ ? "bg-brand-pink/30 text-gray-800"
37343
37616
  : feedback.type === "feature"
37344
- ? "bg-blue-100 text-blue-800"
37617
+ ? "bg-brand-green/30 text-gray-800"
37345
37618
  : "bg-gray-100 text-gray-700"), children: feedback.type === "copy-amendment"
37346
37619
  ? "Copy"
37347
37620
  : "General" })), jsx("span", { className: cn("px-2 py-0.5 rounded-full text-[10px] font-medium uppercase", priorityColors[feedback.priority]), children: feedback.priority }), jsx("span", { className: "text-xs text-gray-400", children: formatDate(feedback.createdAt) })] })] }), !isReadOnly && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Status" }), jsx("div", { className: "flex gap-1.5", children: statusOptions.map((option) => (jsx("div", { onClick: () => handleStatusChange(option.value), className: cn("flex-1 py-1.5 px-2 rounded-lg text-[10px] font-medium border cursor-pointer transition-all text-center", feedback.status === option.value
37348
- ? "bg-[#C2D1D9] text-white border-[#C2D1D9]"
37349
- : "bg-white text-gray-600 border-gray-200 hover:border-indigo-300"), children: option.label }, option.value))) })] })), feedback.screenshot && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-xs font-semibold text-gray-800 mb-2 uppercase tracking-wide", children: "Screenshot" }), jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md cursor-pointer group", onClick: () => setShowFullScreenshot(true), children: [jsx("img", { src: feedback.screenshot, alt: "Feedback screenshot", className: "w-full block" }), jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-all flex items-center justify-center", children: jsx("div", { className: "bg-white rounded-full p-3 shadow-xl transform scale-90 group-hover:scale-100 transition-transform", children: jsx(ZoomInIcon, { className: "w-6 h-6 text-gray-700" }) }) })] })] })), feedback.description && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Description" }), jsx("p", { className: "text-sm text-gray-600 m-0 whitespace-pre-wrap", children: feedback.description })] })), feedback.tags && feedback.tags.length > 0 && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Tags" }), jsx("div", { className: "flex flex-wrap gap-1.5", children: feedback.tags.map((tag) => (jsx("span", { className: "px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs", children: tag }, tag))) })] })), feedback.pageMetadata && (jsxs("div", { className: "mb-4 p-2.5 bg-gray-50 rounded-lg", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Page Info" }), jsxs("div", { className: "text-xs text-gray-500 space-y-1", children: [jsxs("div", { className: "truncate", children: [jsx("span", { className: "font-medium", children: "URL:" }), " ", feedback.pageMetadata.url] }), jsxs("div", { children: [jsx("span", { className: "font-medium", children: "Screen:" }), " ", feedback.pageMetadata.screenWidth, "x", feedback.pageMetadata.screenHeight] }), jsxs("div", { children: [jsx("span", { className: "font-medium", children: "User Agent:" }), " ", feedback.pageMetadata.userAgent] })] })] })), jsxs("div", { className: "mb-4", children: [jsxs("label", { className: "block text-xs font-medium text-gray-700 mb-2", children: ["Comments (", ((_b = feedback.comments) === null || _b === void 0 ? void 0 : _b.length) || 0, ")"] }), feedback.comments && feedback.comments.length > 0 && (jsx("div", { className: "space-y-3 mb-4", children: feedback.comments.map((comment) => (jsxs("div", { className: "p-3 bg-white border border-gray-200 rounded-xl shadow-sm", children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("div", { className: "w-8 h-8 rounded-full bg-gradient-to-br from-[#C2D1D9] to-purple-500 flex items-center justify-center shadow-md", children: jsx(UserIcon, { className: "w-4 h-4 text-white" }) }), jsx("span", { className: "text-xs font-semibold text-gray-900", children: comment.createdBy.name }), jsx("span", { className: "text-[10px] text-gray-500 font-medium", children: formatDate(comment.createdAt) })] }), jsx("p", { className: "text-sm text-gray-700 m-0 ml-10 whitespace-pre-wrap", children: comment.content || comment.text })] }, comment.id))) })), jsxs("div", { className: "flex gap-2", children: [jsx("input", { type: "text", value: newComment, onChange: (e) => setNewComment(e.target.value), placeholder: "Add a comment...", className: "flex-1 px-4 py-2.5 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", onKeyDown: (e) => {
37621
+ ? cn(option.color, "border-transparent")
37622
+ : "bg-white text-gray-600 border-gray-200 hover:border-brand-blue"), children: option.label }, option.value))) })] })), feedback.screenshot && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-xs font-semibold text-gray-800 mb-2 uppercase tracking-wide", children: "Screenshot" }), jsxs("div", { className: "relative rounded-xl overflow-hidden shadow-md cursor-pointer group", onClick: () => setShowFullScreenshot(true), children: [jsx("img", { src: feedback.screenshot, alt: "Feedback screenshot", className: "w-full block" }), jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-all flex items-center justify-center", children: jsx("div", { className: "bg-white rounded-full p-3 shadow-xl transform scale-90 group-hover:scale-100 transition-transform", children: jsx(ZoomInIcon, { className: "w-6 h-6 text-gray-700" }) }) })] })] })), feedback.screenRecording && (jsxs("div", { className: "mb-5", children: [jsx("label", { className: "block text-xs font-semibold text-gray-800 mb-2 uppercase tracking-wide", children: "Screen Recording" }), jsx("div", { className: "relative rounded-xl overflow-hidden shadow-md border border-gray-200", children: jsx("video", { src: feedback.screenRecording, controls: true, className: "w-full h-auto bg-black" }) })] })), (feedback.designMockup || feedback.sessionEvents) && (jsxs("div", { className: "flex gap-2 mb-5", children: [feedback.sessionEvents && (jsx("button", { onClick: () => setShowReplay(true), className: "flex-1 bg-gray-100 text-gray-700 py-2.5 rounded-xl text-xs font-semibold hover:bg-gray-200 transition-colors flex items-center justify-center gap-2 border border-gray-200 cursor-pointer", children: "Play Session" })), feedback.designMockup && (jsx("button", { onClick: () => setShowOverlay(true), className: "flex-1 bg-gray-100 text-gray-700 py-2.5 rounded-xl text-xs font-semibold hover:bg-gray-200 transition-colors flex items-center justify-center gap-2 border border-gray-200 cursor-pointer", children: "Overlay Mockup" }))] })), feedback.type === "copy-amendment" && (jsxs(Fragment, { children: [feedback.currentCopy && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Current Copy" }), jsx("div", { className: "bg-red-50 border border-red-200 rounded-lg p-3", children: jsx("p", { className: "text-sm text-gray-800 m-0 whitespace-pre-wrap", children: feedback.currentCopy }) })] })), feedback.newCopy && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "New Copy" }), jsx("div", { className: "bg-green-50 border border-green-200 rounded-lg p-3", children: jsx("p", { className: "text-sm text-gray-800 m-0 whitespace-pre-wrap", children: feedback.newCopy }) })] }))] })), feedback.description && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Description" }), jsx("p", { className: "text-sm text-gray-600 m-0 whitespace-pre-wrap", children: feedback.description })] })), feedback.tags && feedback.tags.length > 0 && (jsxs("div", { className: "mb-4", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Tags" }), jsx("div", { className: "flex flex-wrap gap-1.5", children: feedback.tags.map((tag) => (jsx("span", { className: "px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-xs", children: tag }, tag))) })] })), feedback.pageMetadata && (jsxs("div", { className: "mb-4 p-2.5 bg-gray-50 rounded-lg", children: [jsx("label", { className: "block text-xs font-medium text-gray-700 mb-1.5", children: "Page Info" }), jsxs("div", { className: "text-xs text-gray-500 space-y-1", children: [jsxs("div", { className: "truncate", children: [jsx("span", { className: "font-medium", children: "URL:" }), " ", feedback.pageMetadata.url] }), feedback.pageMetadata.componentName && (jsxs("div", { className: "truncate", children: [jsx("span", { className: "font-medium", children: "Component:" }), " ", jsx("code", { className: "bg-gray-200 px-1.5 py-0.5 rounded text-xs font-mono text-gray-800", children: feedback.pageMetadata.componentName })] })), feedback.pageMetadata.elementInfo && (jsxs("div", { className: "truncate", children: [jsx("span", { className: "font-medium", children: "Element:" }), " ", jsx("code", { className: "bg-blue-50 px-1.5 py-0.5 rounded text-xs font-mono text-blue-800", children: feedback.pageMetadata.elementInfo })] })), jsxs("div", { children: [jsx("span", { className: "font-medium", children: "Screen:" }), " ", feedback.pageMetadata.screenWidth, "x", feedback.pageMetadata.screenHeight] }), jsxs("div", { children: [jsx("span", { className: "font-medium", children: "User Agent:" }), " ", feedback.pageMetadata.userAgent] })] })] })), jsxs("div", { className: "mb-4", children: [jsxs("label", { className: "block text-xs font-medium text-gray-700 mb-2", children: ["Comments (", ((_c = feedback.comments) === null || _c === void 0 ? void 0 : _c.length) || 0, ")"] }), feedback.comments && feedback.comments.length > 0 && (jsx("div", { className: "space-y-3 mb-4", children: feedback.comments.map((comment) => (jsxs("div", { className: "p-3 bg-white border border-gray-200 rounded-xl shadow-sm", children: [jsxs("div", { className: "flex items-center gap-2 mb-2", children: [jsx("div", { className: "w-8 h-8 rounded-full bg-gradient-to-br from-[#C2D1D9] to-purple-500 flex items-center justify-center shadow-md", children: jsx(UserIcon, { className: "w-4 h-4 text-white" }) }), jsx("span", { className: "text-xs font-semibold text-gray-900", children: comment.createdBy.name }), jsx("span", { className: "text-[10px] text-gray-500 font-medium", children: formatDate(comment.createdAt) })] }), jsx("p", { className: "text-sm text-gray-700 m-0 ml-10 whitespace-pre-wrap", children: comment.content || comment.text })] }, comment.id))) })), jsxs("div", { className: "flex gap-2", children: [jsx("input", { type: "text", value: newComment, onChange: (e) => setNewComment(e.target.value), placeholder: "Add a comment...", className: "flex-1 px-4 py-2.5 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", onKeyDown: (e) => {
37350
37623
  if (e.key === "Enter" && !e.shiftKey) {
37351
37624
  e.preventDefault();
37352
37625
  handleAddComment();
37353
37626
  }
37354
- } }), jsx("button", { onClick: handleAddComment, disabled: !newComment.trim() || isAddingComment, className: "p-2.5 rounded-xl border-none text-white cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg transition-all", style: { backgroundColor: "var(--markup-primary)" }, children: jsx(SendIcon, { className: "w-5 h-5" }) })] })] })] }), showFullScreenshot && feedback.screenshot && (jsxs("div", { className: "fixed inset-0 bg-black z-[1000001] flex items-center justify-center", onClick: () => setShowFullScreenshot(false), children: [jsx("button", { onClick: () => setShowFullScreenshot(false), className: "absolute top-4 right-4 p-3 bg-white/10 rounded-full border-none cursor-pointer hover:bg-white/20 transition-colors z-10", children: jsx(CloseIcon, { className: "w-8 h-8 text-white" }) }), jsx("img", { src: feedback.screenshot, alt: "Feedback screenshot", className: "w-full h-full object-contain", onClick: (e) => e.stopPropagation() })] }))] }));
37627
+ } }), jsx("button", { onClick: handleAddComment, disabled: !newComment.trim() || isAddingComment, className: "p-2.5 rounded-xl border-none text-white cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed hover:shadow-lg transition-all", style: { backgroundColor: "var(--markup-primary)" }, children: jsx(SendIcon, { className: "w-5 h-5" }) })] })] })] }), showFullScreenshot &&
37628
+ feedback.screenshot &&
37629
+ createPortal(jsxs("div", { className: "fixed inset-0 bg-black/90 backdrop-blur-sm z-[2147483647] flex items-center justify-center p-10 animate-in fade-in duration-200", onClick: () => setShowFullScreenshot(false), children: [jsx("button", { onClick: () => setShowFullScreenshot(false), className: "absolute top-6 right-6 p-2 bg-white/10 rounded-full border-none cursor-pointer hover:bg-white/20 transition-colors z-10", children: jsx(CloseIcon, { className: "w-8 h-8 text-white" }) }), jsx("img", { src: feedback.screenshot, alt: "Feedback screenshot", className: "max-w-full max-h-full object-contain rounded-lg shadow-2xl", onClick: (e) => e.stopPropagation() })] }), document.body), showReplay && sessionActions && sessionActions.length > 0 && createPortal(jsx("div", { className: "fixed inset-0 z-[10000] bg-black/80 backdrop-blur-sm flex items-center justify-center p-5 animate-in fade-in duration-200", onClick: () => setShowReplay(false), children: jsxs("div", { className: "bg-white rounded-2xl overflow-hidden shadow-2xl max-w-[900px] w-full flex flex-col max-h-[90vh]", onClick: (e) => e.stopPropagation(), children: [jsxs("div", { className: "p-4 border-b border-gray-100 flex items-center justify-between bg-gray-50", children: [jsxs("div", { children: [jsx("h3", { className: "font-semibold text-gray-900", children: "Session Activity Timeline" }), jsx("p", { className: "text-xs text-gray-600 mt-1", children: "User actions recorded during feedback creation" })] }), jsx("button", { onClick: () => setShowReplay(false), className: "p-2 hover:bg-gray-200 rounded-full transition-colors cursor-pointer text-gray-500 hover:text-gray-900 border-none bg-transparent", children: jsx(CloseIcon, { className: "w-5 h-5" }) })] }), jsx("div", { className: "flex-1 overflow-y-auto p-4", children: jsx("div", { className: "space-y-2", children: sessionActions.map((action, idx) => {
37630
+ var _a;
37631
+ const baseTime = ((_a = sessionActions[0]) === null || _a === void 0 ? void 0 : _a.timestamp) || action.timestamp;
37632
+ return (jsxs("div", { className: "flex items-start gap-3 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors", children: [jsxs("div", { className: "flex-shrink-0 w-20 text-xs font-mono text-gray-600", children: [jsx("div", { children: formatTimestamp(action.timestamp) }), jsx("div", { className: "text-gray-400", children: getRelativeTime(action.timestamp, baseTime) })] }), jsxs("div", { className: "flex-1 min-w-0", children: [action.type === 'click' && (jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsx("span", { className: "inline-block w-2 h-2 bg-blue-500 rounded-full" }), jsx("span", { className: "font-medium text-gray-900", children: "Clicked" })] }), jsxs("div", { className: "text-sm text-gray-700 font-mono bg-white px-2 py-1 rounded border border-gray-200", children: ["<", action.data.elementTag || 'unknown', action.data.elementId && ` id="${action.data.elementId}"`, action.data.elementClass && ` class="${action.data.elementClass}"`, ">"] }), action.data.elementText && (jsxs("div", { className: "mt-1 text-sm text-gray-600", children: ["Text: ", jsxs("span", { className: "italic", children: ["\"", action.data.elementText.substring(0, 100), action.data.elementText.length > 100 ? '...' : '', "\""] })] }))] })), action.type === 'navigation' && (jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsx("span", { className: "inline-block w-2 h-2 bg-purple-500 rounded-full" }), jsx("span", { className: "font-medium text-gray-900", children: "Navigated" })] }), jsx("div", { className: "text-sm text-gray-700 break-all", children: action.data.url })] })), action.type === 'scroll' && (jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsx("span", { className: "inline-block w-2 h-2 bg-green-500 rounded-full" }), jsx("span", { className: "font-medium text-gray-900", children: "Scrolled" })] }), jsxs("div", { className: "text-sm text-gray-700", children: ["Position: ", action.data.scrollY, "px"] })] })), action.type === 'input' && (jsxs("div", { children: [jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsx("span", { className: "inline-block w-2 h-2 bg-orange-500 rounded-full" }), jsx("span", { className: "font-medium text-gray-900", children: "Input" })] }), jsxs("div", { className: "text-sm text-gray-700", children: [action.data.inputType, " field"] })] }))] })] }, idx));
37633
+ }) }) })] }) }), document.body), showOverlay && feedback.designMockup && createPortal(jsxs("div", { className: "fixed inset-0 z-[10000] pointer-events-none", children: [jsx("img", { src: feedback.designMockup, style: { opacity: overlayOpacity / 100 }, className: "w-full h-full object-contain object-top", alt: "Design Mockup Overlay" }), jsxs("div", { className: "fixed bottom-10 left-1/2 -translate-x-1/2 bg-white/90 backdrop-blur border border-gray-200 shadow-xl rounded-full p-2 px-6 flex items-center gap-4 pointer-events-auto", children: [jsx("span", { className: "text-xs font-bold text-gray-500 uppercase whitespace-nowrap", children: "Overlay Opacity" }), jsx("input", { type: "range", min: "0", max: "100", value: overlayOpacity, onChange: (e) => setOverlayOpacity(Number(e.target.value)), className: "w-32 accent-brand-blue" }), jsx("button", { onClick: () => setShowOverlay(false), className: "ml-2 hover:bg-gray-200 p-1.5 rounded-full border-none bg-transparent", children: jsx(CloseIcon, { className: "w-4 h-4 text-gray-500" }) })] })] }), document.body)] }));
37355
37634
  };
37356
37635
 
37357
37636
  const signUp = async (email, password) => {
@@ -37403,18 +37682,82 @@ const AuthForm = ({ onSuccess }) => {
37403
37682
  };
37404
37683
  return (jsx("div", { className: "flex flex-col items-center justify-center h-full p-5", children: jsxs("div", { className: "w-full max-w-md", children: [jsxs("div", { className: "bg-white rounded-2xl shadow-2xl p-8", children: [jsx("h2", { className: "text-2xl font-bold text-gray-900 mb-2 text-center", children: mode === "login" ? "Sign In" : "Create Account" }), jsx("p", { className: "text-gray-600 text-sm text-center mb-6", children: mode === "login"
37405
37684
  ? "Sign in to submit and manage feedback"
37406
- : "Create an account to get started" }), jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [jsxs("div", { children: [jsx("label", { htmlFor: "email", className: "block text-sm font-medium text-gray-700 mb-2", children: "Email Address" }), jsx("input", { id: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#C2D1D9] focus:border-transparent transition-all", placeholder: "you@example.com" })] }), jsxs("div", { children: [jsx("label", { htmlFor: "password", className: "block text-sm font-medium text-gray-700 mb-2", children: "Password" }), jsx("input", { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#C2D1D9] focus:border-transparent transition-all", placeholder: "Minimum 6 characters" })] }), error && (jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm", children: error })), jsx("div", { onClick: handleSubmit, className: "w-full bg-[#c2d1d9] text-white py-2.5 px-4 rounded-lg font-medium hover:bg-[#c2d1d9]/80 focus:ring-4 focus:ring-blue transition-all cursor-pointer text-center", children: loading
37685
+ : "Create an account to get started" }), jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [jsxs("div", { children: [jsx("label", { htmlFor: "email", className: "block text-sm font-medium text-gray-700 mb-2", children: "Email Address" }), jsx("input", { id: "email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#C2D1D9] focus:border-transparent transition-all", placeholder: "you@example.com" })] }), jsxs("div", { children: [jsx("label", { htmlFor: "password", className: "block text-sm font-medium text-gray-700 mb-2", children: "Password" }), jsx("input", { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#C2D1D9] focus:border-transparent transition-all", placeholder: "Minimum 6 characters" })] }), error && (jsx("div", { className: "bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm", children: error })), jsx("div", { onClick: handleSubmit, className: "w-full bg-[#c2d1d9] text-black py-2.5 px-4 rounded-lg font-medium hover:bg-[#c2d1d9]/80 focus:ring-4 focus:ring-blue transition-all cursor-pointer text-center", children: loading
37407
37686
  ? "Please wait..."
37408
37687
  : mode === "login"
37409
37688
  ? "Sign In"
37410
37689
  : "Create Account" })] }), jsx("div", { className: "mt-6 text-center", children: jsx("div", { onClick: () => {
37411
37690
  setMode(mode === "login" ? "signup" : "login");
37412
37691
  setError(null);
37413
- }, className: "bg-gray-500 text-white w-full font-medium cursor-pointer", children: mode === "login"
37692
+ }, className: "bg-pink text-black w-full font-medium cursor-pointer hover:underline", children: mode === "login"
37414
37693
  ? "Don't have an account? Sign up"
37415
37694
  : "Already have an account? Sign in" }) })] }), jsx("p", { className: "text-center text-xs text-gray-500 mt-6", children: "Your feedback will be associated with your account" })] }) }));
37416
37695
  };
37417
37696
 
37697
+ const VIEWPORT_PRESETS = [
37698
+ { name: "iPhone SE", width: 375, height: 667, deviceType: "mobile" },
37699
+ { name: "iPhone 12 Pro", width: 390, height: 844, deviceType: "mobile" },
37700
+ { name: "iPhone 14 Pro Max", width: 430, height: 932, deviceType: "mobile" },
37701
+ { name: "iPad Mini", width: 768, height: 1024, deviceType: "tablet" },
37702
+ { name: "iPad Pro", width: 1024, height: 1366, deviceType: "tablet" },
37703
+ { name: "Desktop", width: 1920, height: 1080, deviceType: "desktop" },
37704
+ ];
37705
+ const ViewportControls = () => {
37706
+ const { viewportMode, setViewportMode, isOpen: isWidgetOpen } = useMarkupStore();
37707
+ const [isExpanded, setIsExpanded] = require$$0.useState(false);
37708
+ const dropdownRef = require$$0.useRef(null);
37709
+ const handlePresetClick = (preset) => {
37710
+ setViewportMode({ width: preset.width, height: preset.height });
37711
+ setIsExpanded(false);
37712
+ };
37713
+ const handleReset = () => {
37714
+ setViewportMode(null);
37715
+ setIsExpanded(false);
37716
+ };
37717
+ const isActivePreset = (preset) => {
37718
+ return (viewportMode === null || viewportMode === void 0 ? void 0 : viewportMode.width) === preset.width && (viewportMode === null || viewportMode === void 0 ? void 0 : viewportMode.height) === preset.height;
37719
+ };
37720
+ // Close dropdown on outside click
37721
+ require$$0.useEffect(() => {
37722
+ if (!isExpanded)
37723
+ return;
37724
+ const handleClickOutside = (event) => {
37725
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
37726
+ setIsExpanded(false);
37727
+ }
37728
+ };
37729
+ document.addEventListener('mousedown', handleClickOutside);
37730
+ return () => document.removeEventListener('mousedown', handleClickOutside);
37731
+ }, [isExpanded]);
37732
+ // Handle ESC key to close dropdown or reset viewport
37733
+ require$$0.useEffect(() => {
37734
+ const handleKeyDown = (event) => {
37735
+ if (event.key === 'Escape') {
37736
+ if (isExpanded) {
37737
+ setIsExpanded(false);
37738
+ event.preventDefault();
37739
+ }
37740
+ else if (viewportMode) {
37741
+ handleReset();
37742
+ event.preventDefault();
37743
+ }
37744
+ }
37745
+ };
37746
+ document.addEventListener('keydown', handleKeyDown);
37747
+ return () => document.removeEventListener('keydown', handleKeyDown);
37748
+ }, [isExpanded, viewportMode]);
37749
+ // Don't show controls when widget is not open
37750
+ if (!isWidgetOpen)
37751
+ return null;
37752
+ return (jsxs("div", { ref: dropdownRef, className: "fixed top-4 left-1/2 -translate-x-1/2 z-[999998] flex items-center gap-2", children: [!viewportMode ? (jsxs("button", { onClick: () => setIsExpanded(!isExpanded), className: "bg-white border-2 border-gray-300 rounded-xl px-4 py-2 shadow-lg hover:shadow-xl transition-all flex items-center gap-2 text-sm font-medium text-gray-700 hover:text-gray-900 hover:border-gray-400", children: [jsx(DesktopIcon, { className: "w-4 h-4" }), "Responsive Mode"] })) : (jsxs("div", { className: "bg-white border-2 border-gray-300 rounded-xl px-4 py-2 shadow-lg flex items-center gap-3", children: [jsxs("span", { className: "text-sm font-medium text-gray-700", children: [viewportMode.width, " \u00D7 ", viewportMode.height] }), jsx("button", { onClick: () => setIsExpanded(!isExpanded), className: "text-gray-600 hover:text-gray-900 transition-colors", children: jsx(DesktopIcon, { className: "w-4 h-4" }) }), jsx("button", { onClick: handleReset, className: "px-2 py-1 text-xs font-medium text-red-600 hover:text-white hover:bg-red-600 border border-red-600 rounded transition-colors", children: "Reset" })] })), isExpanded && (jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 mt-2 bg-white border-2 border-gray-200 rounded-xl shadow-2xl p-3 min-w-[240px]", children: jsxs("div", { className: "space-y-2", children: [jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide px-2 mb-2", children: "Mobile" }), VIEWPORT_PRESETS.filter((p) => p.deviceType === "mobile").map((preset) => (jsxs("button", { onClick: () => handlePresetClick(preset), className: cn("w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-all text-left group", isActivePreset(preset)
37753
+ ? "bg-blue-50 border border-blue-200"
37754
+ : "hover:bg-gray-100"), children: [jsx(MobileIcon, { 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))), jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide px-2 mt-4 mb-2", children: "Tablet" }), VIEWPORT_PRESETS.filter((p) => p.deviceType === "tablet").map((preset) => (jsxs("button", { onClick: () => handlePresetClick(preset), className: cn("w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-all text-left group", isActivePreset(preset)
37755
+ ? "bg-blue-50 border border-blue-200"
37756
+ : "hover:bg-gray-100"), children: [jsx(TabletIcon, { 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))), jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wide px-2 mt-4 mb-2", children: "Desktop" }), VIEWPORT_PRESETS.filter((p) => p.deviceType === "desktop").map((preset) => (jsxs("button", { onClick: () => handlePresetClick(preset), className: cn("w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-all text-left group", isActivePreset(preset)
37757
+ ? "bg-blue-50 border border-blue-200"
37758
+ : "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)))] }) }))] }));
37759
+ };
37760
+
37418
37761
  const useFirebaseSync = () => {
37419
37762
  const config = useMarkupStore((state) => state.config);
37420
37763
  const setCurrentUser = useMarkupStore((state) => state.setCurrentUser);
@@ -37509,6 +37852,109 @@ const useFirebaseSync = () => {
37509
37852
  }, [config.projectId, setFeedbackItems, isAuthenticated]);
37510
37853
  };
37511
37854
 
37855
+ const useSessionRecording = () => {
37856
+ const actionsRef = useRef([]);
37857
+ const maxActions = 50; // Keep last 50 actions
37858
+ useEffect(() => {
37859
+ if (typeof window === 'undefined')
37860
+ return;
37861
+ const recordAction = (action) => {
37862
+ if (actionsRef.current.length >= maxActions) {
37863
+ actionsRef.current.shift();
37864
+ }
37865
+ actionsRef.current.push(action);
37866
+ };
37867
+ // Record clicks
37868
+ const handleClick = (e) => {
37869
+ var _a;
37870
+ const target = e.target;
37871
+ if (target.closest('[data-markup-widget]'))
37872
+ return;
37873
+ recordAction({
37874
+ type: 'click',
37875
+ timestamp: Date.now(),
37876
+ data: {
37877
+ elementTag: target.tagName.toLowerCase(),
37878
+ elementText: ((_a = target.textContent) === null || _a === void 0 ? void 0 : _a.slice(0, 50).trim()) || undefined,
37879
+ elementId: target.id || undefined,
37880
+ elementClass: target.className && typeof target.className === 'string'
37881
+ ? target.className.split(' ').slice(0, 3).join(' ')
37882
+ : undefined,
37883
+ }
37884
+ });
37885
+ };
37886
+ // Record navigation
37887
+ const handleNavigation = () => {
37888
+ recordAction({
37889
+ type: 'navigation',
37890
+ timestamp: Date.now(),
37891
+ data: {
37892
+ url: window.location.href
37893
+ }
37894
+ });
37895
+ };
37896
+ // Record scroll (throttled)
37897
+ let scrollTimeout;
37898
+ const handleScroll = () => {
37899
+ clearTimeout(scrollTimeout);
37900
+ scrollTimeout = setTimeout(() => {
37901
+ recordAction({
37902
+ type: 'scroll',
37903
+ timestamp: Date.now(),
37904
+ data: {
37905
+ scrollY: window.scrollY
37906
+ }
37907
+ });
37908
+ }, 500);
37909
+ };
37910
+ // Record input events
37911
+ const handleInput = (e) => {
37912
+ const target = e.target;
37913
+ if (target.closest('[data-markup-widget]'))
37914
+ return;
37915
+ recordAction({
37916
+ type: 'input',
37917
+ timestamp: Date.now(),
37918
+ data: {
37919
+ elementTag: target.tagName.toLowerCase(),
37920
+ elementId: target.id || undefined,
37921
+ inputType: target.type || undefined,
37922
+ }
37923
+ });
37924
+ };
37925
+ // Record initial page load
37926
+ recordAction({
37927
+ type: 'navigation',
37928
+ timestamp: Date.now(),
37929
+ data: { url: window.location.href }
37930
+ });
37931
+ document.addEventListener('click', handleClick, true);
37932
+ window.addEventListener('popstate', handleNavigation);
37933
+ window.addEventListener('scroll', handleScroll, { passive: true });
37934
+ document.addEventListener('input', handleInput, true);
37935
+ return () => {
37936
+ document.removeEventListener('click', handleClick, true);
37937
+ window.removeEventListener('popstate', handleNavigation);
37938
+ window.removeEventListener('scroll', handleScroll);
37939
+ document.removeEventListener('input', handleInput, true);
37940
+ clearTimeout(scrollTimeout);
37941
+ };
37942
+ }, []);
37943
+ const getSessionEvents = useCallback(() => {
37944
+ if (actionsRef.current.length === 0) {
37945
+ return undefined;
37946
+ }
37947
+ return JSON.stringify(actionsRef.current);
37948
+ }, []);
37949
+ const clearSession = useCallback(() => {
37950
+ actionsRef.current = [];
37951
+ }, []);
37952
+ return {
37953
+ getSessionEvents,
37954
+ clearSession
37955
+ };
37956
+ };
37957
+
37512
37958
  const MarkupWidget = ({ config: userConfig, }) => {
37513
37959
  var _a;
37514
37960
  // Initialize Firebase synchronously on first render only
@@ -37523,9 +37969,76 @@ const MarkupWidget = ({ config: userConfig, }) => {
37523
37969
  console.error('Failed to initialize Firebase:', error);
37524
37970
  }
37525
37971
  }
37526
- const { isOpen, setIsOpen, activeTab, setActiveTab, selectedFeedback, setSelectedFeedback, feedbackItems, addFeedbackItem, config, setConfig, reset, isAuthenticated, currentUser, } = useMarkupStore();
37972
+ const { isOpen, setIsOpen, activeTab, setActiveTab, selectedFeedback, setSelectedFeedback, feedbackItems, addFeedbackItem, removeFeedbackItem, updateFeedback, config, setConfig, reset, isAuthenticated, currentUser, viewportMode, setViewportMode, } = useMarkupStore();
37527
37973
  // Initialize Firebase sync
37528
37974
  useFirebaseSync();
37975
+ // Initialize Session Recording (Feature 2)
37976
+ const { getSessionEvents } = useSessionRecording();
37977
+ // Track last clicked element for component detection (Feature 4)
37978
+ const lastClickedElementRef = useRef(null);
37979
+ const [lastClickedText, setLastClickedText] = useState('');
37980
+ useEffect(() => {
37981
+ const handleClick = (e) => {
37982
+ var _a;
37983
+ // Store the clicked element (but ignore clicks on the widget itself)
37984
+ const target = e.target;
37985
+ if (!target.closest('[data-markup-widget]')) {
37986
+ lastClickedElementRef.current = target;
37987
+ // If user clicked on text content, capture it for copy amendment
37988
+ // Get the immediate text content (not children)
37989
+ let textContent = '';
37990
+ if (target.childNodes.length > 0) {
37991
+ // Try to get direct text nodes only
37992
+ for (const node of Array.from(target.childNodes)) {
37993
+ if (node.nodeType === Node.TEXT_NODE) {
37994
+ textContent += node.textContent || '';
37995
+ }
37996
+ }
37997
+ textContent = textContent.trim();
37998
+ }
37999
+ // If no direct text, fall back to full textContent
38000
+ if (!textContent) {
38001
+ textContent = ((_a = target.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
38002
+ }
38003
+ if (textContent && textContent.length > 0 && textContent.length < 500) {
38004
+ setLastClickedText(textContent);
38005
+ }
38006
+ else {
38007
+ setLastClickedText('');
38008
+ }
38009
+ }
38010
+ };
38011
+ document.addEventListener('click', handleClick, true);
38012
+ return () => document.removeEventListener('click', handleClick, true);
38013
+ }, []);
38014
+ // Apply viewport mode styling to document (like browser dev tools)
38015
+ useEffect(() => {
38016
+ if (viewportMode) {
38017
+ const root = document.documentElement;
38018
+ const body = document.body;
38019
+ // Apply dark background to body
38020
+ body.style.background = 'rgba(0, 0, 0, 0.5)';
38021
+ body.style.overflow = 'visible';
38022
+ // Apply viewport constraints with smooth transition
38023
+ root.style.transition = 'max-width 0.3s ease-in-out, margin 0.3s ease-in-out, background 0.2s ease-in-out';
38024
+ root.style.maxWidth = `${viewportMode.width}px`;
38025
+ root.style.margin = '0 auto';
38026
+ root.style.border = '2px solid #999';
38027
+ root.style.background = '#ffffff'; // Ensure content area has white background
38028
+ root.style.boxShadow = '0 0 50px rgba(0, 0, 0, 0.3)';
38029
+ return () => {
38030
+ // Reset to full width
38031
+ root.style.transition = '';
38032
+ root.style.maxWidth = '';
38033
+ root.style.margin = '';
38034
+ root.style.border = '';
38035
+ root.style.background = '';
38036
+ root.style.boxShadow = '';
38037
+ body.style.overflow = '';
38038
+ body.style.background = '';
38039
+ };
38040
+ }
38041
+ }, [viewportMode]);
37529
38042
  useEffect(() => {
37530
38043
  if (userConfig) {
37531
38044
  setConfig(userConfig);
@@ -37597,7 +38110,8 @@ const MarkupWidget = ({ config: userConfig, }) => {
37597
38110
  }, [setSelectedFeedback]);
37598
38111
  const handleBackFromDetail = useCallback(() => {
37599
38112
  setSelectedFeedback(null);
37600
- }, [setSelectedFeedback]);
38113
+ setViewportMode(null);
38114
+ }, [setSelectedFeedback, setViewportMode]);
37601
38115
  const handleLogout = useCallback(async () => {
37602
38116
  try {
37603
38117
  await logOut();
@@ -37610,6 +38124,15 @@ const MarkupWidget = ({ config: userConfig, }) => {
37610
38124
  const handleAuthSuccess = useCallback(() => {
37611
38125
  setActiveTab("create");
37612
38126
  }, [setActiveTab]);
38127
+ const handleDeleteFeedback = useCallback((id) => {
38128
+ removeFeedbackItem(id);
38129
+ setSelectedFeedback(null);
38130
+ }, [removeFeedbackItem, setSelectedFeedback]);
38131
+ const handleUpdateFeedback = useCallback((updatedFeedback) => {
38132
+ updateFeedback(updatedFeedback);
38133
+ // Also update selected feedback if it's the same one (it should be)
38134
+ setSelectedFeedback(updatedFeedback);
38135
+ }, [updateFeedback, setSelectedFeedback]);
37613
38136
  const openFeedbackCount = feedbackItems.filter((f) => f.status === "open").length;
37614
38137
  ({
37615
38138
  ["--markup-primary"]: config.primaryColor || "#6366f1",
@@ -37617,15 +38140,15 @@ const MarkupWidget = ({ config: userConfig, }) => {
37617
38140
  ? adjustColor(config.primaryColor, -20)
37618
38141
  : "#4f46e5",
37619
38142
  });
37620
- return (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: {
37621
- transform: isOpen
37622
- ? "translateY(-50%) translateX(0)"
37623
- : "translateY(-50%) translateX(calc(100% - 40px))",
37624
- }, "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 p-2 border-black border text-black rounded-xl ", 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 })) : (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"
37625
- ? "bg-gray-200"
37626
- : "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"
37627
- ? "bg-gray-200"
37628
- : "border-white"), onClick: () => setActiveTab("list"), children: ["View All (", feedbackItems.length, ")"] })] }), jsxs("div", { className: "flex-1 overflow-y-auto p-5", children: [activeTab === "create" && (jsx(FeedbackForm, { onSubmit: handleSubmitFeedback, onCancel: handleCancel })), activeTab === "list" && (jsx(FeedbackList, { feedbackItems: feedbackItems, onSelectFeedback: handleSelectFeedback }))] })] }))] })] }));
38143
+ 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: {
38144
+ transform: isOpen
38145
+ ? "translateY(-50%) translateX(0)"
38146
+ : "translateY(-50%) translateX(calc(100% - 40px))",
38147
+ }, "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"
38148
+ ? "bg-gray-200"
38149
+ : "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"
38150
+ ? "bg-gray-200"
38151
+ : "border-white"), onClick: () => setActiveTab("list"), children: ["View All (", feedbackItems.length, ")"] })] }), jsxs("div", { className: "flex-1 overflow-y-auto p-5", children: [activeTab === "create" && (jsx(FeedbackForm, { onSubmit: handleSubmitFeedback, onCancel: handleCancel, getSessionEvents: getSessionEvents, lastClickedElement: lastClickedElementRef.current, lastClickedText: lastClickedText })), activeTab === "list" && (jsx(FeedbackList, { feedbackItems: feedbackItems, onSelectFeedback: handleSelectFeedback }))] })] }))] })] })] }));
37629
38152
  };
37630
38153
  function adjustColor(color, amount) {
37631
38154
  const clamp = (val) => Math.min(255, Math.max(0, val));