@robotical/webapp-types 3.15.18 → 3.15.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-types/src/components/disposables/buttons/SVGImageButton/index.d.ts +3 -1
- package/dist-types/src/components/disposables/buttons/SVGImageButton/index.js +3 -3
- package/dist-types/src/components/disposables/buttons/SimpleButton/index.d.ts +3 -1
- package/dist-types/src/components/disposables/buttons/SimpleButton/index.js +79 -3
- package/dist-types/src/components/modals/ SensorsDashboardModal/index.js +1 -1
- package/dist-types/src/components/modals/DetailedFeedbackModal/index.d.ts +4 -2
- package/dist-types/src/components/modals/DetailedFeedbackModal/index.js +151 -17
- package/dist-types/src/components/modals/LEDLightsOrQRVerificationModal/index.js +2 -2
- package/dist-types/src/components/modals/VerificationModalPhoneApp/index.js +1 -1
- package/dist-types/src/utils/feedback/feedbackPromptPolicy.d.ts +39 -0
- package/dist-types/src/utils/feedback/feedbackPromptPolicy.js +183 -0
- package/dist-types/src/utils/feedback/submitFeedback.d.ts +25 -0
- package/dist-types/src/utils/feedback/submitFeedback.js +145 -0
- package/package.json +1 -1
|
@@ -9,6 +9,8 @@ type SVGImageButtonProps = {
|
|
|
9
9
|
titleColour?: string;
|
|
10
10
|
SVGColour?: string;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
buttonType?: "button" | "submit" | "reset";
|
|
12
14
|
};
|
|
13
|
-
export default function SVGImageButton({ onClick, title, subtitle, SVGImage, backgroundColour, titleColour, SVGColour, disabled, }: SVGImageButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export default function SVGImageButton({ onClick, title, subtitle, SVGImage, backgroundColour, titleColour, SVGColour, disabled, ariaLabel, buttonType, }: SVGImageButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
14
16
|
export {};
|
|
@@ -12,8 +12,8 @@ var __assign = (this && this.__assign) || function () {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import "./styles.css";
|
|
14
14
|
export default function SVGImageButton(_a) {
|
|
15
|
-
var onClick = _a.onClick, title = _a.title, subtitle = _a.subtitle, SVGImage = _a.SVGImage, backgroundColour = _a.backgroundColour, titleColour = _a.titleColour, SVGColour = _a.SVGColour, disabled = _a.disabled;
|
|
16
|
-
return (_jsxs("
|
|
15
|
+
var onClick = _a.onClick, title = _a.title, subtitle = _a.subtitle, SVGImage = _a.SVGImage, backgroundColour = _a.backgroundColour, titleColour = _a.titleColour, SVGColour = _a.SVGColour, disabled = _a.disabled, ariaLabel = _a.ariaLabel, _b = _a.buttonType, buttonType = _b === void 0 ? "button" : _b;
|
|
16
|
+
return (_jsxs("button", __assign({ type: buttonType, className: subtitle
|
|
17
17
|
? "svg-image-button-container"
|
|
18
|
-
: "svg-image-button-container-no-sub",
|
|
18
|
+
: "svg-image-button-container-no-sub", disabled: disabled, "aria-label": ariaLabel || title, onClick: onClick, style: { backgroundColor: disabled ? "#f0f0f0" : backgroundColour, cursor: disabled ? "not-allowed" : "pointer" } }, { children: [_jsx("p", __assign({ className: subtitle ? "svg-image-button-title" : "svg-image-button-title-no-sub", style: { color: titleColour } }, { children: title })), subtitle && _jsx("p", __assign({ className: "svg-image-button-subtitle" }, { children: subtitle })), _jsx("div", __assign({ className: "svg-image-button-image-container" }, { children: _jsx(SVGImage, { fill: SVGColour }) }))] })));
|
|
19
19
|
}
|
|
@@ -9,6 +9,8 @@ type SimpleButtonProps = {
|
|
|
9
9
|
otherStyles?: React.CSSProperties;
|
|
10
10
|
borderColour?: string;
|
|
11
11
|
textColour?: string;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
buttonType?: "button" | "submit" | "reset";
|
|
12
14
|
};
|
|
13
|
-
export default function SimpleButton({ onClick, title, titleSize, disabled, colour, otherStyles, borderColour, textColour }: SimpleButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export default function SimpleButton({ onClick, title, titleSize, disabled, colour, otherStyles, borderColour, textColour, ariaLabel, buttonType, }: SimpleButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
14
16
|
export {};
|
|
@@ -11,8 +11,84 @@ var __assign = (this && this.__assign) || function () {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
13
|
import "./styles.css";
|
|
14
|
+
import { BLACK, WHITE } from "../../../../styles/colors";
|
|
15
|
+
var NAMED_COLORS = {
|
|
16
|
+
black: [0, 0, 0],
|
|
17
|
+
white: [255, 255, 255],
|
|
18
|
+
};
|
|
19
|
+
var parseHexChannel = function (value) { return Number.parseInt(value, 16); };
|
|
20
|
+
var parseColour = function (colour) {
|
|
21
|
+
if (!colour) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
var normalized = colour.trim().toLowerCase();
|
|
25
|
+
if (NAMED_COLORS[normalized]) {
|
|
26
|
+
return NAMED_COLORS[normalized];
|
|
27
|
+
}
|
|
28
|
+
if (normalized.startsWith("#")) {
|
|
29
|
+
var hex = normalized.slice(1);
|
|
30
|
+
if (hex.length === 3) {
|
|
31
|
+
return [
|
|
32
|
+
parseHexChannel("".concat(hex[0]).concat(hex[0])),
|
|
33
|
+
parseHexChannel("".concat(hex[1]).concat(hex[1])),
|
|
34
|
+
parseHexChannel("".concat(hex[2]).concat(hex[2])),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
if (hex.length === 6) {
|
|
38
|
+
return [
|
|
39
|
+
parseHexChannel(hex.slice(0, 2)),
|
|
40
|
+
parseHexChannel(hex.slice(2, 4)),
|
|
41
|
+
parseHexChannel(hex.slice(4, 6)),
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
var rgbMatch = normalized.match(/rgba?\(([^)]+)\)/);
|
|
47
|
+
if (!rgbMatch) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
var channels = rgbMatch[1]
|
|
51
|
+
.split(",")
|
|
52
|
+
.slice(0, 3)
|
|
53
|
+
.map(function (channel) { return Number.parseFloat(channel.trim()); });
|
|
54
|
+
if (channels.length !== 3 || channels.some(function (channel) { return Number.isNaN(channel); })) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return [channels[0], channels[1], channels[2]];
|
|
58
|
+
};
|
|
59
|
+
var getRelativeLuminance = function (_a) {
|
|
60
|
+
var red = _a[0], green = _a[1], blue = _a[2];
|
|
61
|
+
var transform = function (channel) {
|
|
62
|
+
var normalized = channel / 255;
|
|
63
|
+
return normalized <= 0.03928
|
|
64
|
+
? normalized / 12.92
|
|
65
|
+
: Math.pow((normalized + 0.055) / 1.055, 2.4);
|
|
66
|
+
};
|
|
67
|
+
return (0.2126 * transform(red) +
|
|
68
|
+
0.7152 * transform(green) +
|
|
69
|
+
0.0722 * transform(blue));
|
|
70
|
+
};
|
|
71
|
+
var getContrastRatio = function (colourA, colourB) {
|
|
72
|
+
var luminanceA = getRelativeLuminance(colourA);
|
|
73
|
+
var luminanceB = getRelativeLuminance(colourB);
|
|
74
|
+
var lighter = Math.max(luminanceA, luminanceB);
|
|
75
|
+
var darker = Math.min(luminanceA, luminanceB);
|
|
76
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
77
|
+
};
|
|
78
|
+
var BLACK_RGB = [38, 38, 38];
|
|
79
|
+
var WHITE_RGB = [245, 245, 245];
|
|
80
|
+
var getAccessibleTextColour = function (backgroundColour) {
|
|
81
|
+
var parsedBackground = parseColour(backgroundColour);
|
|
82
|
+
if (!parsedBackground) {
|
|
83
|
+
return BLACK;
|
|
84
|
+
}
|
|
85
|
+
var blackContrast = getContrastRatio(parsedBackground, BLACK_RGB);
|
|
86
|
+
var whiteContrast = getContrastRatio(parsedBackground, WHITE_RGB);
|
|
87
|
+
return blackContrast >= whiteContrast ? BLACK : WHITE;
|
|
88
|
+
};
|
|
14
89
|
export default function SimpleButton(_a) {
|
|
15
|
-
var onClick = _a.onClick, title = _a.title, titleSize = _a.titleSize, disabled = _a.disabled, colour = _a.colour, otherStyles = _a.otherStyles, borderColour = _a.borderColour, textColour = _a.textColour;
|
|
16
|
-
|
|
17
|
-
|
|
90
|
+
var onClick = _a.onClick, title = _a.title, titleSize = _a.titleSize, disabled = _a.disabled, colour = _a.colour, otherStyles = _a.otherStyles, borderColour = _a.borderColour, textColour = _a.textColour, ariaLabel = _a.ariaLabel, _b = _a.buttonType, buttonType = _b === void 0 ? "button" : _b;
|
|
91
|
+
var resolvedTextColour = textColour || getAccessibleTextColour(colour);
|
|
92
|
+
return (_jsx("button", __assign({ type: buttonType, className: "simple-button-component " +
|
|
93
|
+
(disabled ? "simple-button-component-disabled" : ""), disabled: disabled, "aria-label": ariaLabel, style: __assign(__assign({}, otherStyles), { fontSize: titleSize, backgroundColor: colour, border: borderColour && "1px solid ".concat(borderColour), color: resolvedTextColour }), onClick: onClick }, { children: title })));
|
|
18
94
|
}
|
|
@@ -54,7 +54,7 @@ function SensorsDashboardModal() {
|
|
|
54
54
|
resizeObserver.disconnect();
|
|
55
55
|
};
|
|
56
56
|
}, [betaSignRef]);
|
|
57
|
-
return (_jsxs(_Fragment, { children: [_jsx("
|
|
57
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", __assign({ type: "button", style: { visibility: showBetaSign ? "visible" : "hidden" }, className: styles.betaSign, onClick: function () {
|
|
58
58
|
return modalState.setModal(createElement(DetailedFeedbackModal), "Anonymous Bug Report");
|
|
59
59
|
}, ref: betaSignRef }, { children: _jsx(BetaSignSVG, {}) })), _jsx(SensorsDashboard, { isInModal: true })] }));
|
|
60
60
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { FeedbackRating } from "../../../utils/feedback/submitFeedback";
|
|
1
2
|
type Props = {
|
|
2
|
-
otherInfoObject?:
|
|
3
|
+
otherInfoObject?: Record<string, unknown>;
|
|
4
|
+
rating?: FeedbackRating;
|
|
3
5
|
};
|
|
4
|
-
export default function DetailedFeedbackModal({ otherInfoObject }: Props): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default function DetailedFeedbackModal({ otherInfoObject, rating, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
5
7
|
export {};
|
|
@@ -9,31 +9,165 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
23
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
+
function step(op) {
|
|
26
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
+
switch (op[0]) {
|
|
31
|
+
case 0: case 1: t = op; break;
|
|
32
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
+
default:
|
|
36
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
+
if (t[2]) _.ops.pop();
|
|
41
|
+
_.trys.pop(); continue;
|
|
42
|
+
}
|
|
43
|
+
op = body.call(thisArg, _);
|
|
44
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
12
48
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
|
-
import { useState } from "react";
|
|
49
|
+
import { useRef, useState } from "react";
|
|
14
50
|
import { useTranslation } from "react-i18next";
|
|
15
51
|
import modalState from "../../../state-observables/modal/ModalState";
|
|
52
|
+
import { submitFeedback, } from "../../../utils/feedback/submitFeedback";
|
|
53
|
+
import { getFeedbackPromptPageFromContext, markFeedbackPromptSubmitted, } from "../../../utils/feedback/feedbackPromptPolicy";
|
|
16
54
|
import styles from "./styles.module.css";
|
|
17
55
|
import SimpleButton from "../../disposables/buttons/SimpleButton";
|
|
18
56
|
export default function DetailedFeedbackModal(_a) {
|
|
19
|
-
var
|
|
57
|
+
var _this = this;
|
|
58
|
+
var otherInfoObject = _a.otherInfoObject, rating = _a.rating;
|
|
20
59
|
var t = useTranslation().t;
|
|
21
60
|
var _b = useState(""), report = _b[0], setReport = _b[1];
|
|
22
61
|
var _c = useState(""), email = _c[0], setEmail = _c[1];
|
|
23
62
|
var _d = useState(false), reportSent = _d[0], setReportSent = _d[1];
|
|
24
|
-
var
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
var _e = useState(false), isSubmitting = _e[0], setIsSubmitting = _e[1];
|
|
64
|
+
var hasSubmittedFeedback = useRef(false);
|
|
65
|
+
var submitRatingOnlyOnClose = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
66
|
+
var page, error_1;
|
|
67
|
+
var _a, _b;
|
|
68
|
+
return __generator(this, function (_c) {
|
|
69
|
+
switch (_c.label) {
|
|
70
|
+
case 0:
|
|
71
|
+
if (!rating || hasSubmittedFeedback.current) {
|
|
72
|
+
return [2 /*return*/, true];
|
|
73
|
+
}
|
|
74
|
+
_c.label = 1;
|
|
75
|
+
case 1:
|
|
76
|
+
_c.trys.push([1, 3, , 4]);
|
|
77
|
+
return [4 /*yield*/, submitFeedback({
|
|
78
|
+
rating: rating,
|
|
79
|
+
context: otherInfoObject,
|
|
80
|
+
source: "emoji-modal",
|
|
81
|
+
})];
|
|
82
|
+
case 2:
|
|
83
|
+
_c.sent();
|
|
84
|
+
page = getFeedbackPromptPageFromContext(otherInfoObject);
|
|
85
|
+
if (page) {
|
|
86
|
+
markFeedbackPromptSubmitted(page);
|
|
87
|
+
}
|
|
88
|
+
hasSubmittedFeedback.current = true;
|
|
89
|
+
return [2 /*return*/, true];
|
|
90
|
+
case 3:
|
|
91
|
+
error_1 = _c.sent();
|
|
92
|
+
(_b = (_a = window.applicationManager) === null || _a === void 0 ? void 0 : _a.toaster) === null || _b === void 0 ? void 0 : _b.error("There was an error sending your feedback. Please try again later.");
|
|
93
|
+
return [2 /*return*/, false];
|
|
94
|
+
case 4: return [2 /*return*/];
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}); };
|
|
98
|
+
var onClose = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
99
|
+
var wasSubmitted;
|
|
100
|
+
return __generator(this, function (_a) {
|
|
101
|
+
switch (_a.label) {
|
|
102
|
+
case 0:
|
|
103
|
+
if (isSubmitting) {
|
|
104
|
+
return [2 /*return*/];
|
|
105
|
+
}
|
|
106
|
+
if (!rating || hasSubmittedFeedback.current) {
|
|
107
|
+
modalState.closeModal();
|
|
108
|
+
return [2 /*return*/];
|
|
109
|
+
}
|
|
110
|
+
setIsSubmitting(true);
|
|
111
|
+
return [4 /*yield*/, submitRatingOnlyOnClose()];
|
|
112
|
+
case 1:
|
|
113
|
+
wasSubmitted = _a.sent();
|
|
114
|
+
setIsSubmitting(false);
|
|
115
|
+
if (wasSubmitted) {
|
|
116
|
+
modalState.closeModal();
|
|
117
|
+
}
|
|
118
|
+
return [2 /*return*/];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}); };
|
|
122
|
+
var onReport = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
123
|
+
var confirm_1, page, error_2;
|
|
124
|
+
var _a, _b;
|
|
125
|
+
return __generator(this, function (_c) {
|
|
126
|
+
switch (_c.label) {
|
|
127
|
+
case 0:
|
|
128
|
+
if (!report || isSubmitting)
|
|
129
|
+
return [2 /*return*/];
|
|
130
|
+
if (!email) {
|
|
131
|
+
confirm_1 = window.confirm(t("detailed_feedback_modal.no_email_confirm") || "");
|
|
132
|
+
if (!confirm_1)
|
|
133
|
+
return [2 /*return*/];
|
|
134
|
+
}
|
|
135
|
+
setIsSubmitting(true);
|
|
136
|
+
_c.label = 1;
|
|
137
|
+
case 1:
|
|
138
|
+
_c.trys.push([1, 3, 4, 5]);
|
|
139
|
+
return [4 /*yield*/, submitFeedback({
|
|
140
|
+
rating: rating,
|
|
141
|
+
message: report,
|
|
142
|
+
email: email,
|
|
143
|
+
context: otherInfoObject,
|
|
144
|
+
source: "detailed-feedback",
|
|
145
|
+
})];
|
|
146
|
+
case 2:
|
|
147
|
+
_c.sent();
|
|
148
|
+
page = getFeedbackPromptPageFromContext(otherInfoObject);
|
|
149
|
+
if (page) {
|
|
150
|
+
markFeedbackPromptSubmitted(page);
|
|
151
|
+
}
|
|
152
|
+
hasSubmittedFeedback.current = true;
|
|
153
|
+
setReportSent(true);
|
|
154
|
+
modalState.updateModalProps({ modalTitle: "🚀" });
|
|
155
|
+
setTimeout(function () { return modalState.closeModal(); }, 1500);
|
|
156
|
+
return [3 /*break*/, 5];
|
|
157
|
+
case 3:
|
|
158
|
+
error_2 = _c.sent();
|
|
159
|
+
(_b = (_a = window.applicationManager) === null || _a === void 0 ? void 0 : _a.toaster) === null || _b === void 0 ? void 0 : _b.error("There was an error sending your feedback. Please try again later.");
|
|
160
|
+
return [3 /*break*/, 5];
|
|
161
|
+
case 4:
|
|
162
|
+
setIsSubmitting(false);
|
|
163
|
+
return [7 /*endfinally*/];
|
|
164
|
+
case 5: return [2 /*return*/];
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}); };
|
|
168
|
+
return (_jsx("div", __assign({ className: styles.bugReportModalContainer }, { children: reportSent ? (t("detailed_feedback_modal.thank_you", "Thank you for your feedback!")) : (_jsxs(_Fragment, { children: [_jsxs("div", __assign({ className: styles.bugReportModalInfo }, { children: [t("detailed_feedback_modal.info", "Please help us improve! Please let us know if you spot any irregularities/bugs. "), _jsx("strong", __assign({ className: styles.bugReportModalInfoStrong }, { children: t("detailed_feedback_modal.info_strong", "You can optionally leave your contact details so we can reach out and help you") })), ". ", t("detailed_feedback_modal.thank_you", "Thank you!")] })), _jsxs("div", __assign({ className: styles.bugReportModalForm }, { children: [_jsx("textarea", { className: styles.bugReportModalTextInput, placeholder: t("detailed_feedback_modal.report_placeholder", "Your report...") || "Your report...", onChange: function (e) { return setReport(e.target.value); }, value: report }), _jsx("label", __assign({ className: styles.bugReportModalEmailLabel }, { children: t("detailed_feedback_modal.email_label", "Provide your email if you want us to get back to you with a solution!") })), _jsx("input", { type: "email", className: styles.bugReportModalEmailInput, placeholder: t("detailed_feedback_modal.email_placeholder", "Email (optional)") || "Email (optional)", onChange: function (e) { return setEmail(e.target.value); }, value: email })] })), _jsxs("div", __assign({ className: styles.bugReportModalButtons }, { children: [_jsx(SimpleButton, { onClick: function () {
|
|
169
|
+
void onClose();
|
|
170
|
+
}, title: t("detailed_feedback_modal.cancel", "Cancel"), disabled: isSubmitting }), _jsx(SimpleButton, { onClick: function () {
|
|
171
|
+
void onReport();
|
|
172
|
+
}, title: t("detailed_feedback_modal.report", "Report"), disabled: isSubmitting })] }))] })) })));
|
|
39
173
|
}
|
|
@@ -110,9 +110,9 @@ export default function LEDLightsOrQRVerificationModal(_a) {
|
|
|
110
110
|
}); };
|
|
111
111
|
return (_jsxs("div", __assign({ className: styles.container }, { children: [_jsxs("div", __assign({ className: styles.robotContext }, { children: [_jsx("p", __assign({ className: styles.robotContextEyebrow }, { children: t("connection.connecting_to", "Connecting to") })), _jsx("h3", __assign({ className: styles.robotContextTitle }, { children: targetRaftType })), allowSwitchToMarty && (_jsx("button", __assign({ type: "button", className: styles.switchRobotButton, onClick: function () { return modalState.closeModal({ action: "switch-to-marty" }); } }, { children: t("connection.connect_marty_instead", "Connect Marty instead") })))] })), _jsxs("div", __assign({ className: styles.bluetoothNotice }, { children: [_jsx(BluetoothSVG, { className: styles.bluetoothIcon, fill: MAIN_BLUE, "aria-hidden": "true", focusable: "false" }), _jsx("span", __assign({ className: styles.bluetoothText }, { children: t("configuration_screen.bluetooth") }))] })), _jsxs("div", { children: [_jsx("p", __assign({ className: styles.robotContextSubtitle }, { children: t("connection.cog_verification_prompt", "Choose how you want to verify your Cog connection.") })), !isButtonToConnectVisible ? _jsx("div", __assign({ className: styles.cards }, { children: options.map(function (_a) {
|
|
112
112
|
var id = _a.id, title = _a.title, subtitle = _a.subtitle, icon = _a.icon;
|
|
113
|
-
return (_jsxs("
|
|
113
|
+
return (_jsxs("button", __assign({ type: "button", className: qrSelected && id === 'qr' ? [styles.card, styles.qrselected].join(" ") : styles.card, onClick: function () { return handleSelect(id); }, "aria-label": title, "aria-pressed": id === "qr" ? qrSelected : false }, { children: [_jsx("div", __assign({ className: styles.icon }, { children: icon })), _jsx("h3", __assign({ className: styles.title }, { children: title })), _jsx("p", __assign({ className: styles.subtitle }, { children: subtitle }))] }), id));
|
|
114
114
|
}) })) :
|
|
115
|
-
_jsx("div", __assign({ className: styles.cogNote }, { children: _jsxs("strong", { children: ["Don't forget to ", _jsx("span", __assign({ className: styles.cogNoteHighlight }, { children: "turn on" })), " your Cog!"] }) }))] }), qrSelected && _jsx(QRContent, { setSerialNo: setSerialNoFromQR }), _jsxs("div", __assign({ className: styles.buttonsContainer }, { children: [isButtonToConnectVisible && _jsx("button", __assign({ className: styles.connectButton, onClick: handleConnect }, { children: t('connection.qr_connect_button') })), _jsx("button", __assign({ className: styles.cancelButton, onClick: function () { return modalState.closeModal({ action: "cancel" }); } }, { children: t('connection.cancel') }))] }))] })));
|
|
115
|
+
_jsx("div", __assign({ className: styles.cogNote }, { children: _jsxs("strong", { children: ["Don't forget to ", _jsx("span", __assign({ className: styles.cogNoteHighlight }, { children: "turn on" })), " your Cog!"] }) }))] }), qrSelected && _jsx(QRContent, { setSerialNo: setSerialNoFromQR }), _jsxs("div", __assign({ className: styles.buttonsContainer }, { children: [isButtonToConnectVisible && _jsx("button", __assign({ type: "button", className: styles.connectButton, onClick: handleConnect }, { children: t('connection.qr_connect_button') })), _jsx("button", __assign({ type: "button", className: styles.cancelButton, onClick: function () { return modalState.closeModal({ action: "cancel" }); } }, { children: t('connection.cancel') }))] }))] })));
|
|
116
116
|
}
|
|
117
117
|
function QRContent(_a) {
|
|
118
118
|
var _this = this;
|
|
@@ -219,7 +219,7 @@ function VerificationModalPhoneApp(_a) {
|
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
221
|
contentJSX = _jsxs("div", __assign({ className: "verification-modal-container" }, { children: [_jsxs("div", __assign({ className: "verification-modal-martys-container" }, { children: [isScanning && _jsxs("div", __assign({ style: { margin: "auto", width: "50px", height: "50px" } }, { children: [" ", _jsx(LoadingSpinner, {}), " "] })), foundRICsState.map(function (raftObj, raftIdx) {
|
|
222
|
-
return _jsxs(Fragment, { children: [_jsxs("
|
|
222
|
+
return _jsxs(Fragment, { children: [_jsxs("button", __assign({ type: "button", className: "verification-modal-marty-name-row-container", onClick: function () { return onSelectRaft(raftIdx); }, "aria-label": "".concat(t("verification_modal_phone.select_robot", "Select robot"), " ").concat(raftObj._localName) }, { children: [_jsx("p", __assign({ className: "verification-modal-marty-name" }, { children: raftObj._localName })), _jsx("div", __assign({ className: "verification-modal-marty-signal-container" }, { children: _jsx(RICSignal, { signalStrength: raftObj._rssi }) })), _jsx(BluetoothSVG, { fill: MAIN_BLUE, "aria-hidden": "true" })] })), selectedRICIdx === raftIdx && _jsxs("div", { children: [_jsx("p", __assign({ className: "verification-modal-martys-back-hint" }, { children: isLoading ? t("verification_modal_phone.loading", "Loading...") : t("verification_modal_phone.look_on_back", { raftType: connectedRaft === null || connectedRaft === void 0 ? void 0 : connectedRaft.type }) })), _jsxs("div", __assign({ className: "verification-modal-led-row-container" }, { children: [_jsx(LEDs, { coloursArr: randomColours, connectedRaft: connectedRaft }), !isLoading && _jsx(SimpleButton, { onClick: onCancel, title: t("verification_modal_phone.no", "No"), borderColour: "red", colour: "white", textColour: "red" }), !isLoading && _jsx(SimpleButton, { onClick: onYes, title: t("connection.yes") })] }))] })] }, raftIdx);
|
|
223
223
|
})] })), _jsxs("div", __assign({ className: "verification-modal-bottom-btns-container" }, { children: [_jsx(SVGImageButton, { onClick: onScanAgain, title: t("connection.scan_again"), SVGImage: RefreshSVG, backgroundColour: MAIN_BLUE, titleColour: WHITE, SVGColour: WHITE, disabled: isScanning }), _jsx("div", { className: "dummy-gap" }), _jsx(SimpleButton, { onClick: onCancel, title: t("connection.cancel") })] }))] }));
|
|
224
224
|
}
|
|
225
225
|
return contentJSX;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type FeedbackPromptPage = string;
|
|
2
|
+
export type FeedbackPromptDecisionReason = "invalid-page" | "not-ready" | "insufficient-engagement" | "shown-this-session" | "recently-submitted" | "global-cooldown" | "page-cooldown";
|
|
3
|
+
export type FeedbackPromptDecision = {
|
|
4
|
+
canShow: boolean;
|
|
5
|
+
reason?: FeedbackPromptDecisionReason;
|
|
6
|
+
engagementMs: number;
|
|
7
|
+
};
|
|
8
|
+
export type FeedbackPromptPageState = {
|
|
9
|
+
lastShownAt?: number;
|
|
10
|
+
lastSubmittedAt?: number;
|
|
11
|
+
lastDismissedAt?: number;
|
|
12
|
+
};
|
|
13
|
+
export type FeedbackPromptState = {
|
|
14
|
+
version: typeof FEEDBACK_PROMPT_POLICY_VERSION;
|
|
15
|
+
lastShownAt?: number;
|
|
16
|
+
lastSubmittedAt?: number;
|
|
17
|
+
lastDismissedAt?: number;
|
|
18
|
+
pages: Record<FeedbackPromptPage, FeedbackPromptPageState>;
|
|
19
|
+
};
|
|
20
|
+
export type CanShowFeedbackPromptInput = {
|
|
21
|
+
page: FeedbackPromptPage;
|
|
22
|
+
isReady?: boolean;
|
|
23
|
+
readyAt?: number | null;
|
|
24
|
+
now?: number;
|
|
25
|
+
};
|
|
26
|
+
export declare const FEEDBACK_PROMPT_POLICY_VERSION = "v1";
|
|
27
|
+
export declare const FEEDBACK_PROMPT_STORAGE_KEY: string;
|
|
28
|
+
export declare const FEEDBACK_PROMPT_SESSION_STORAGE_KEY: string;
|
|
29
|
+
export declare const FEEDBACK_PROMPT_MIN_READY_ENGAGEMENT_MS: number;
|
|
30
|
+
export declare const FEEDBACK_PROMPT_GLOBAL_COOLDOWN_MS: number;
|
|
31
|
+
export declare const FEEDBACK_PROMPT_PAGE_COOLDOWN_MS: number;
|
|
32
|
+
export declare const FEEDBACK_PROMPT_SUBMITTED_COOLDOWN_MS: number;
|
|
33
|
+
export declare function readFeedbackPromptState(): FeedbackPromptState;
|
|
34
|
+
export declare function writeFeedbackPromptState(state: FeedbackPromptState): void;
|
|
35
|
+
export declare function canShowFeedbackPrompt({ page, isReady, readyAt, now, }: CanShowFeedbackPromptInput): FeedbackPromptDecision;
|
|
36
|
+
export declare function markFeedbackPromptShown(page: FeedbackPromptPage, now?: number): void;
|
|
37
|
+
export declare function markFeedbackPromptSubmitted(page: FeedbackPromptPage, now?: number): void;
|
|
38
|
+
export declare function markFeedbackPromptDismissed(page: FeedbackPromptPage, now?: number): void;
|
|
39
|
+
export declare function getFeedbackPromptPageFromContext(context?: Record<string, unknown> | null): string | null;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export var FEEDBACK_PROMPT_POLICY_VERSION = "v1";
|
|
2
|
+
export var FEEDBACK_PROMPT_STORAGE_KEY = "marty-web-app.feedbackPromptPolicy.".concat(FEEDBACK_PROMPT_POLICY_VERSION);
|
|
3
|
+
export var FEEDBACK_PROMPT_SESSION_STORAGE_KEY = "marty-web-app.feedbackPromptPolicy.".concat(FEEDBACK_PROMPT_POLICY_VERSION, ".shownThisSession");
|
|
4
|
+
export var FEEDBACK_PROMPT_MIN_READY_ENGAGEMENT_MS = 90 * 1000;
|
|
5
|
+
export var FEEDBACK_PROMPT_GLOBAL_COOLDOWN_MS = 14 * 24 * 60 * 60 * 1000;
|
|
6
|
+
export var FEEDBACK_PROMPT_PAGE_COOLDOWN_MS = 45 * 24 * 60 * 60 * 1000;
|
|
7
|
+
export var FEEDBACK_PROMPT_SUBMITTED_COOLDOWN_MS = 90 * 24 * 60 * 60 * 1000;
|
|
8
|
+
function createEmptyState() {
|
|
9
|
+
return {
|
|
10
|
+
version: FEEDBACK_PROMPT_POLICY_VERSION,
|
|
11
|
+
pages: {},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function normalizePage(page) {
|
|
15
|
+
return page.trim();
|
|
16
|
+
}
|
|
17
|
+
function isRecord(value) {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function getTimestamp(value) {
|
|
21
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
22
|
+
}
|
|
23
|
+
function normalizeState(rawState) {
|
|
24
|
+
if (!isRecord(rawState)) {
|
|
25
|
+
return createEmptyState();
|
|
26
|
+
}
|
|
27
|
+
var pages = {};
|
|
28
|
+
var rawPages = isRecord(rawState.pages) ? rawState.pages : {};
|
|
29
|
+
Object.entries(rawPages).forEach(function (_a) {
|
|
30
|
+
var page = _a[0], rawPageState = _a[1];
|
|
31
|
+
if (!isRecord(rawPageState)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
pages[page] = {
|
|
35
|
+
lastShownAt: getTimestamp(rawPageState.lastShownAt),
|
|
36
|
+
lastSubmittedAt: getTimestamp(rawPageState.lastSubmittedAt),
|
|
37
|
+
lastDismissedAt: getTimestamp(rawPageState.lastDismissedAt),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
version: FEEDBACK_PROMPT_POLICY_VERSION,
|
|
42
|
+
lastShownAt: getTimestamp(rawState.lastShownAt),
|
|
43
|
+
lastSubmittedAt: getTimestamp(rawState.lastSubmittedAt),
|
|
44
|
+
lastDismissedAt: getTimestamp(rawState.lastDismissedAt),
|
|
45
|
+
pages: pages,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function getLocalStorage() {
|
|
49
|
+
return typeof window !== "undefined" ? window.localStorage : null;
|
|
50
|
+
}
|
|
51
|
+
function getSessionStorage() {
|
|
52
|
+
return typeof window !== "undefined" ? window.sessionStorage : null;
|
|
53
|
+
}
|
|
54
|
+
export function readFeedbackPromptState() {
|
|
55
|
+
var storage = getLocalStorage();
|
|
56
|
+
if (!storage) {
|
|
57
|
+
return createEmptyState();
|
|
58
|
+
}
|
|
59
|
+
var rawValue = null;
|
|
60
|
+
try {
|
|
61
|
+
rawValue = storage.getItem(FEEDBACK_PROMPT_STORAGE_KEY);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return createEmptyState();
|
|
65
|
+
}
|
|
66
|
+
if (!rawValue) {
|
|
67
|
+
return createEmptyState();
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return normalizeState(JSON.parse(rawValue));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
try {
|
|
74
|
+
storage.removeItem(FEEDBACK_PROMPT_STORAGE_KEY);
|
|
75
|
+
}
|
|
76
|
+
catch (removeError) {
|
|
77
|
+
// Ignore cleanup failures; falling back to an empty state is sufficient.
|
|
78
|
+
}
|
|
79
|
+
return createEmptyState();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function writeFeedbackPromptState(state) {
|
|
83
|
+
var storage = getLocalStorage();
|
|
84
|
+
if (!storage) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
storage.setItem(FEEDBACK_PROMPT_STORAGE_KEY, JSON.stringify(normalizeState(state)));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Storage failures should never block the user from leaving an activity.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function hasShownThisSession() {
|
|
95
|
+
var _a;
|
|
96
|
+
try {
|
|
97
|
+
return ((_a = getSessionStorage()) === null || _a === void 0 ? void 0 : _a.getItem(FEEDBACK_PROMPT_SESSION_STORAGE_KEY)) === "true";
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function markShownThisSession() {
|
|
104
|
+
var _a;
|
|
105
|
+
try {
|
|
106
|
+
(_a = getSessionStorage()) === null || _a === void 0 ? void 0 : _a.setItem(FEEDBACK_PROMPT_SESSION_STORAGE_KEY, "true");
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// Storage may be unavailable in private browsing or embedded contexts.
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function updateFeedbackPromptState(page, updater, now) {
|
|
113
|
+
if (now === void 0) { now = Date.now(); }
|
|
114
|
+
var normalizedPage = normalizePage(page);
|
|
115
|
+
if (!normalizedPage) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
var state = readFeedbackPromptState();
|
|
119
|
+
var pageState = state.pages[normalizedPage] || {};
|
|
120
|
+
updater(state, pageState, now);
|
|
121
|
+
state.pages[normalizedPage] = pageState;
|
|
122
|
+
writeFeedbackPromptState(state);
|
|
123
|
+
}
|
|
124
|
+
export function canShowFeedbackPrompt(_a) {
|
|
125
|
+
var page = _a.page, _b = _a.isReady, isReady = _b === void 0 ? true : _b, readyAt = _a.readyAt, _c = _a.now, now = _c === void 0 ? Date.now() : _c;
|
|
126
|
+
var normalizedPage = normalizePage(page);
|
|
127
|
+
var hasReadyAt = readyAt !== null && readyAt !== undefined;
|
|
128
|
+
var engagementMs = hasReadyAt ? Math.max(0, now - readyAt) : 0;
|
|
129
|
+
if (!normalizedPage) {
|
|
130
|
+
return { canShow: false, reason: "invalid-page", engagementMs: engagementMs };
|
|
131
|
+
}
|
|
132
|
+
if (!isReady || !hasReadyAt) {
|
|
133
|
+
return { canShow: false, reason: "not-ready", engagementMs: engagementMs };
|
|
134
|
+
}
|
|
135
|
+
if (engagementMs < FEEDBACK_PROMPT_MIN_READY_ENGAGEMENT_MS) {
|
|
136
|
+
return { canShow: false, reason: "insufficient-engagement", engagementMs: engagementMs };
|
|
137
|
+
}
|
|
138
|
+
if (hasShownThisSession()) {
|
|
139
|
+
return { canShow: false, reason: "shown-this-session", engagementMs: engagementMs };
|
|
140
|
+
}
|
|
141
|
+
var state = readFeedbackPromptState();
|
|
142
|
+
var pageState = state.pages[normalizedPage];
|
|
143
|
+
if (state.lastSubmittedAt &&
|
|
144
|
+
now - state.lastSubmittedAt < FEEDBACK_PROMPT_SUBMITTED_COOLDOWN_MS) {
|
|
145
|
+
return { canShow: false, reason: "recently-submitted", engagementMs: engagementMs };
|
|
146
|
+
}
|
|
147
|
+
if (state.lastShownAt &&
|
|
148
|
+
now - state.lastShownAt < FEEDBACK_PROMPT_GLOBAL_COOLDOWN_MS) {
|
|
149
|
+
return { canShow: false, reason: "global-cooldown", engagementMs: engagementMs };
|
|
150
|
+
}
|
|
151
|
+
if ((pageState === null || pageState === void 0 ? void 0 : pageState.lastShownAt) &&
|
|
152
|
+
now - pageState.lastShownAt < FEEDBACK_PROMPT_PAGE_COOLDOWN_MS) {
|
|
153
|
+
return { canShow: false, reason: "page-cooldown", engagementMs: engagementMs };
|
|
154
|
+
}
|
|
155
|
+
return { canShow: true, engagementMs: engagementMs };
|
|
156
|
+
}
|
|
157
|
+
export function markFeedbackPromptShown(page, now) {
|
|
158
|
+
if (now === void 0) { now = Date.now(); }
|
|
159
|
+
updateFeedbackPromptState(page, function (state, pageState) {
|
|
160
|
+
state.lastShownAt = now;
|
|
161
|
+
pageState.lastShownAt = now;
|
|
162
|
+
}, now);
|
|
163
|
+
markShownThisSession();
|
|
164
|
+
}
|
|
165
|
+
export function markFeedbackPromptSubmitted(page, now) {
|
|
166
|
+
if (now === void 0) { now = Date.now(); }
|
|
167
|
+
updateFeedbackPromptState(page, function (state, pageState) {
|
|
168
|
+
state.lastSubmittedAt = now;
|
|
169
|
+
pageState.lastSubmittedAt = now;
|
|
170
|
+
}, now);
|
|
171
|
+
markShownThisSession();
|
|
172
|
+
}
|
|
173
|
+
export function markFeedbackPromptDismissed(page, now) {
|
|
174
|
+
if (now === void 0) { now = Date.now(); }
|
|
175
|
+
updateFeedbackPromptState(page, function (state, pageState) {
|
|
176
|
+
state.lastDismissedAt = now;
|
|
177
|
+
pageState.lastDismissedAt = now;
|
|
178
|
+
}, now);
|
|
179
|
+
}
|
|
180
|
+
export function getFeedbackPromptPageFromContext(context) {
|
|
181
|
+
var page = context === null || context === void 0 ? void 0 : context.page;
|
|
182
|
+
return typeof page === "string" && page.trim() ? page.trim() : null;
|
|
183
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type FeedbackRating = 1 | 2 | 3 | 4 | 5;
|
|
2
|
+
export type FeedbackSource = "emoji-modal" | "detailed-feedback" | "help-bug-report";
|
|
3
|
+
export type SubmitFeedbackInput = {
|
|
4
|
+
rating?: FeedbackRating;
|
|
5
|
+
message?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
context?: Record<string, unknown> | null;
|
|
8
|
+
source: FeedbackSource;
|
|
9
|
+
};
|
|
10
|
+
export type FeedbackRecord = {
|
|
11
|
+
rating: FeedbackRating | null;
|
|
12
|
+
message: string | null;
|
|
13
|
+
email: string | null;
|
|
14
|
+
source: FeedbackSource;
|
|
15
|
+
context: Record<string, unknown> | null;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
19
|
+
export type SubmitFeedbackResult = {
|
|
20
|
+
record: FeedbackRecord;
|
|
21
|
+
slackDelivered: boolean;
|
|
22
|
+
};
|
|
23
|
+
export declare function buildFeedbackText(record: FeedbackRecord): string;
|
|
24
|
+
export declare function createFeedbackRecord(input: SubmitFeedbackInput): FeedbackRecord;
|
|
25
|
+
export declare function submitFeedback(input: SubmitFeedbackInput): Promise<SubmitFeedbackResult>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import Logger from "../../services/logger/Logger";
|
|
38
|
+
import randomHashGenerator from "../helpers/randomHashGenerator";
|
|
39
|
+
var SHOW_LOGS = true;
|
|
40
|
+
var TAG = "submitFeedback";
|
|
41
|
+
var WEB_APP_FEEDBACK_DB_URL = "https://web-app-bug-reports-default-rtdb.europe-west1.firebasedatabase.app/";
|
|
42
|
+
var WEB_APP_FEEDBACK_SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/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";
|
|
43
|
+
function normalizeOptionalString(value) {
|
|
44
|
+
var trimmedValue = value === null || value === void 0 ? void 0 : value.trim();
|
|
45
|
+
return trimmedValue ? trimmedValue : null;
|
|
46
|
+
}
|
|
47
|
+
export function buildFeedbackText(record) {
|
|
48
|
+
var lines = ["Source: ".concat(record.source)];
|
|
49
|
+
var page = record.context && typeof record.context.page === "string"
|
|
50
|
+
? record.context.page
|
|
51
|
+
: null;
|
|
52
|
+
if (page) {
|
|
53
|
+
lines.push("Page: ".concat(page));
|
|
54
|
+
}
|
|
55
|
+
if (record.context) {
|
|
56
|
+
var contextWithoutPage = Object.fromEntries(Object.entries(record.context).filter(function (_a) {
|
|
57
|
+
var key = _a[0];
|
|
58
|
+
return key !== "page";
|
|
59
|
+
}));
|
|
60
|
+
if (Object.keys(contextWithoutPage).length > 0) {
|
|
61
|
+
lines.push("Context: ".concat(JSON.stringify(contextWithoutPage)));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (record.rating) {
|
|
65
|
+
lines.push("Rating: ".concat(record.rating, "/5"));
|
|
66
|
+
}
|
|
67
|
+
lines.push("Email: ".concat(record.email || "Anonymous"));
|
|
68
|
+
if (record.message) {
|
|
69
|
+
lines.push("Message: ".concat(record.message));
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
export function createFeedbackRecord(input) {
|
|
74
|
+
var _a;
|
|
75
|
+
var normalizedMessage = normalizeOptionalString(input.message);
|
|
76
|
+
var normalizedEmail = normalizeOptionalString(input.email);
|
|
77
|
+
var timestamp = new Date().toISOString();
|
|
78
|
+
var record = {
|
|
79
|
+
rating: (_a = input.rating) !== null && _a !== void 0 ? _a : null,
|
|
80
|
+
message: normalizedMessage,
|
|
81
|
+
email: normalizedEmail,
|
|
82
|
+
source: input.source,
|
|
83
|
+
context: input.context || null,
|
|
84
|
+
timestamp: timestamp,
|
|
85
|
+
text: "",
|
|
86
|
+
};
|
|
87
|
+
record.text = buildFeedbackText(record);
|
|
88
|
+
return record;
|
|
89
|
+
}
|
|
90
|
+
export function submitFeedback(input) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
92
|
+
var record, firebaseResponse, errorText, slackDelivered, slackResponse, errorText, error_1;
|
|
93
|
+
return __generator(this, function (_a) {
|
|
94
|
+
switch (_a.label) {
|
|
95
|
+
case 0:
|
|
96
|
+
record = createFeedbackRecord(input);
|
|
97
|
+
return [4 /*yield*/, fetch("".concat(WEB_APP_FEEDBACK_DB_URL).concat(randomHashGenerator(), ".json"), {
|
|
98
|
+
method: "PATCH",
|
|
99
|
+
headers: {
|
|
100
|
+
Application: "application/json",
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(record),
|
|
104
|
+
})];
|
|
105
|
+
case 1:
|
|
106
|
+
firebaseResponse = _a.sent();
|
|
107
|
+
if (!!firebaseResponse.ok) return [3 /*break*/, 3];
|
|
108
|
+
return [4 /*yield*/, firebaseResponse.text()];
|
|
109
|
+
case 2:
|
|
110
|
+
errorText = _a.sent();
|
|
111
|
+
Logger.error(SHOW_LOGS, TAG, "Failed to submit feedback to Firebase: ".concat(firebaseResponse.status, " ").concat(errorText));
|
|
112
|
+
throw new Error("Failed to submit feedback");
|
|
113
|
+
case 3:
|
|
114
|
+
slackDelivered = true;
|
|
115
|
+
_a.label = 4;
|
|
116
|
+
case 4:
|
|
117
|
+
_a.trys.push([4, 8, , 9]);
|
|
118
|
+
return [4 /*yield*/, fetch(WEB_APP_FEEDBACK_SLACK_WEBHOOK_URL, {
|
|
119
|
+
method: "POST",
|
|
120
|
+
headers: {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({ text: record.text }),
|
|
124
|
+
})];
|
|
125
|
+
case 5:
|
|
126
|
+
slackResponse = _a.sent();
|
|
127
|
+
if (!!slackResponse.ok) return [3 /*break*/, 7];
|
|
128
|
+
return [4 /*yield*/, slackResponse.text()];
|
|
129
|
+
case 6:
|
|
130
|
+
errorText = _a.sent();
|
|
131
|
+
throw new Error("Failed Slack response: ".concat(slackResponse.status, " ").concat(errorText));
|
|
132
|
+
case 7: return [3 /*break*/, 9];
|
|
133
|
+
case 8:
|
|
134
|
+
error_1 = _a.sent();
|
|
135
|
+
slackDelivered = false;
|
|
136
|
+
Logger.error(SHOW_LOGS, TAG, "Failed to submit feedback to Slack", error_1);
|
|
137
|
+
return [3 /*break*/, 9];
|
|
138
|
+
case 9: return [2 /*return*/, {
|
|
139
|
+
record: record,
|
|
140
|
+
slackDelivered: slackDelivered,
|
|
141
|
+
}];
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|