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