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