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