Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/feedback-ui-custom-styling.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/feedback-ui-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/feedback-ui-positioning-dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/feedback-ui-positioning-modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/feedback-ui-positioning-popover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/toolbar-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 13 additions & 8 deletions packages/browser-sdk/FEEDBACK.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
The Reflag Browser SDK includes a UI you can use to collect feedback from user
about particular flags.

![image](https://github.com/reflagcom/javascript/assets/34348/c387bac1-f2e2-4efd-9dda-5030d76f9532)
![Feedback UI example](/assets/feedback-ui-header.png)

## Global feedback configuration

Expand Down Expand Up @@ -90,6 +90,10 @@ const reflag = new ReflagClient({
position: POSITION_CONFIG, // See positioning section
translations: TRANSLATION_KEYS, // See internationalization section

// Decides which user input options are shown in the widget
// Default: "comment-and-score"
inputMode: "comment-and-score" | "comment-only" | "score-only";

// Trigger side effects with the collected data,
// for example posting it back into your own CRM
onAfterSubmit: (feedback) => {
Expand Down Expand Up @@ -148,6 +152,10 @@ reflagClient.requestFeedback({
position: POSITION_CONFIG, // [Optional] see the positioning section
translations: TRANSLATION_KEYS // [Optional] see the internationalization section

// [Optional] Decides which user input options are shown in the widget
// Default: "comment-and-score"
inputMode: "comment-and-score" | "comment-only" | "score-only";

// [Optional] trigger side effects with the collected data,
// for example sending the feedback to your own CRM
onAfterSubmit: (feedback) => {
Expand Down Expand Up @@ -178,7 +186,7 @@ page. It can be dismissed with the keyboard shortcut `<ESC>` or the dedicated
close button in the top right corner. It is always centered on the page, capturing
focus, and making it the primary interface the user needs to interact with.

![image](https://github.com/reflagcom/javascript/assets/331790/6c6efbd3-cf7d-4d5b-b126-7ac978b2e512)
![Feedback UI with modal positioning](/assets/feedback-ui-positioning-modal.png)

Using a modal is the strongest possible push for feedback. You are interrupting the
user's normal flow, which can cause annoyance. A good use-case for the modal is
Expand All @@ -198,7 +206,7 @@ user's interaction with the rest of the page. It can be dismissed with the dedic
close button, but will automatically disappear after a short time period if the user
does not interact with it.

![image](https://github.com/reflagcom/javascript/assets/331790/30413513-fd5f-4a2c-852a-9b074fa4666c)
![Feedback UI with dialog positioning](/assets/feedback-ui-positioning-dialog.png)

Using a dialog is a soft push for feedback. It lets the user continue their work
with a minimal amount of intrusion. The user can opt-in to respond but is not
Expand Down Expand Up @@ -226,7 +234,7 @@ position: {
A popover that is anchored relative to a DOM-element (typically a button). It can
be dismissed by clicking outside the popover or by pressing the dedicated close button.

![image](https://github.com/reflagcom/javascript/assets/331790/4c5c5597-9ed3-4d4d-90c0-950926d0d967)
![Feedback UI with popover positioning](/assets/feedback-ui-positioning-popover.png)

You can use the popover mode to implement your own button to collect feedback manually.

Expand Down Expand Up @@ -265,8 +273,6 @@ translations by passing an object in the options to either or both of the
These translations will replace the English ones used by the feedback interface.
See examples below.

![image](https://github.com/reflagcom/javascript/assets/331790/68805b38-e9f6-4de5-9f55-188216983e3c)

See [default English localization keys](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/src/feedback/ui/config/defaultTranslations.tsx)
for a reference of what translation keys can be supplied.

Expand All @@ -285,7 +291,6 @@ new ReflagClient({
"Dans quelle mesure êtes-vous satisfait de cette fonctionnalité ?",
QuestionPlaceholder:
"Comment pouvons-nous améliorer cette fonctionnalité ?",
ScoreStatusDescription: "Choisissez une note et laissez un commentaire",
ScoreStatusLoading: "Chargement...",
ScoreStatusReceived: "La note a été reçue !",
ScoreVeryDissatisfiedLabel: "Très insatisfait",
Expand Down Expand Up @@ -351,7 +356,7 @@ properties to your page in your CSS `:root`-scope.

For example, a dark mode theme might look like this:

![image](https://github.com/reflagcom/javascript/assets/34348/5d579b7b-a830-4530-8b40-864488a8597e)
![Feedback UI with custom styling](/assets/feedback-ui-custom-styling.png)

```css
:root {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ await reflagClient.updateUser({ voiceHuddleOptIn: (!isEnabled).toString() });

The Reflag Toolbar is great for toggling flags on/off for yourself to ensure that everything works both when a flag is on and when it's off.

<img width="352" alt="Toolbar screenshot" src="https://github.com/user-attachments/assets/c223df5a-4bd8-49a1-8b4a-ad7001357693" />
![Toolbar example](/assets/toolbar-example.png)

The toolbar will automatically appear on `localhost`. However, it can also be incredibly useful in production.
You have full control over when it appears through the `toolbar` configuration option passed to the `ReflagClient`.
Expand Down
76 changes: 60 additions & 16 deletions packages/browser-sdk/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,67 @@
const urlParams = new URLSearchParams(window.location.search);
const publishableKey = urlParams.get("publishableKey");
const flagKey = urlParams.get("flagKey") ?? "huddles";

const onFeedbackClick = (e) => {
reflag.requestFeedback({
flagKey,
inputMode: "comment-and-score",
position: { type: "POPOVER", anchor: e.currentTarget },
});
};
</script>
<style>
body {
font-family: sans-serif;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Open Sans",
"Helvetica Neue",
sans-serif;
}
#container {
margin: 100px;
display: none;
flex-direction: row;
gap: 12px;
}
#start-huddle {
border: 1px solid black;
padding: 10px;

#container.show {
display: flex;
}

#container button {
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
position: relative;
white-space: nowrap;
vertical-align: middle;
line-height: 1.2;
border-radius: 7px;
font-weight: 550;
padding: 0 12px;
height: 28px;
font-size: 13px;
border: 1px solid #cfd0d8;
color: #09090b;
box-shadow:
0 1px 2px 0 rgba(0, 0, 0, 0.06),
0 1px 1px 0 rgba(0, 0, 0, 0.01);
background: white;
}
</style>
<div id="start-huddle" style="display: none">
<button
onClick="reflag.requestFeedback({flagKey, position: {type: 'POPOVER', anchor: event.currentTarget}})"
>
Give feedback!
</button>
<button onClick="reflag.track(flagKey)">Start huddle</button>
<div id="container">
<!-- <button onClick="reflag.track(flagKey)">Export to CSV</button> -->
<button onClick="onFeedbackClick(event)">Give feedback</button>
</div>

<script type="module">
Expand Down Expand Up @@ -62,13 +106,13 @@
console.log("Flags updated");
const flag = reflag.getFlag(flagKey);

const startHuddleElem = document.getElementById("start-huddle");
const startHuddleElem = document.getElementById("container");
if (flag.isEnabled) {
// show the start-huddle button
if (startHuddleElem) startHuddleElem.style.display = "block";
// show the container button
if (startHuddleElem) startHuddleElem.classList.add("show");
} else {
// hide the start-huddle button
if (startHuddleElem) startHuddleElem.style.display = "none";
// hide the container button
if (startHuddleElem) startHuddleElem.classList.remove("show");
}
});
</script>
Expand Down
1 change: 1 addition & 0 deletions packages/browser-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build": "tsc --project tsconfig.build.json && vite build",
"test": "tsc --project tsconfig.json && vitest -c vitest.config.ts",
"test:e2e": "yarn build && playwright test",
"test:e2e-ui": "yarn build && playwright test --ui",
"test:ci": "tsc --project tsconfig.json && vitest run -c vitest.config.ts --reporter=default --reporter=junit --outputFile=junit.xml && yarn test:e2e",
"coverage": "vitest run --coverage",
"lint": "eslint .",
Expand Down
37 changes: 22 additions & 15 deletions packages/browser-sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RequestFeedbackOptions,
} from "./feedback/feedback";
import * as feedbackLib from "./feedback/ui";
import { OpenFeedbackFormOptions } from "./feedback/ui/types";
import {
CheckEvent,
FallbackFlagOverride,
Expand All @@ -24,6 +25,12 @@ import { showToolbarToggle } from "./toolbar";
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
const isNode = typeof document === "undefined"; // deno supports "window" but not "document" according to https://remix.run/docs/en/main/guides/gotchas

const VALID_INPUT_MODES: Required<OpenFeedbackFormOptions["inputMode"]>[] = [
"comment-and-score",
"comment-only",
"score-only",
];

/**
* (Internal) User context.
*
Expand Down Expand Up @@ -701,6 +708,13 @@ export class ReflagClient {
return;
}

if (options.inputMode && !VALID_INPUT_MODES.includes(options.inputMode)) {
this.logger.error(
"`requestFeedback` call ignored. Invalid `inputMode` provided",
);
return;
}

const feedbackData = {
flagKey: options.flagKey,
companyId:
Expand All @@ -720,29 +734,22 @@ export class ReflagClient {
position: options.position || this.requestFeedbackOptions.position,
translations:
options.translations || this.requestFeedbackOptions.translations,
openWithCommentVisible: options.openWithCommentVisible,
inputMode: options.inputMode,
onClose: options.onClose,
onDismiss: options.onDismiss,
onScoreSubmit: async (data) => {
const res = await this.feedback({
...feedbackData,
...data,
});

if (res) {
const json = await res.json();
return { feedbackId: json.feedbackId };
}
return { feedbackId: undefined };
},
onSubmit: async (data) => {
// Default onSubmit handler
await this.feedback({
const res = await this.feedback({
...feedbackData,
...data,
});

options.onAfterSubmit?.(data);
if (!res) return;

const json = res ? await res.json() : {};
options.onAfterSubmit?.({ ...data, ...json });

return json;
},
});
}, 1);
Expand Down
13 changes: 5 additions & 8 deletions packages/browser-sdk/src/feedback/feedback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ export class AutoFeedback {
message: FeedbackPrompt,
completionHandler: FeedbackPromptCompletionHandler,
) {
let feedbackId: string | undefined = undefined;
const feedbackId: string | undefined = undefined;

await this.feedbackPromptEvent({
promptId: message.promptId,
Expand Down Expand Up @@ -435,14 +435,11 @@ export class AutoFeedback {
feedbackLib.openFeedbackForm({
key: message.featureId,
title: message.question,
onScoreSubmit: async (data) => {
const res = await replyCallback(data);
feedbackId = res.feedbackId;
return { feedbackId: res.feedbackId };
},
onSubmit: async (data) => {
await replyCallback(data);
options.onAfterSubmit?.(data);
const res = await replyCallback(data);
if (!res) return;
options.onAfterSubmit?.({ ...data, ...res });
return res;
},
onDismiss: () => replyCallback(null),
position: this.position,
Expand Down
8 changes: 4 additions & 4 deletions packages/browser-sdk/src/feedback/ui/Button.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
user-select: none;
position: relative;
white-space: nowrap;
height: 2rem;
height: 1.85rem;
padding-inline-start: 0.75rem;
padding-inline-end: 0.75rem;
gap: 0.5em;
Expand All @@ -27,11 +27,11 @@
&.primary {
background-color: var(
--reflag-feedback-dialog-primary-button-background-color,
white
#09090b
);
color: var(--reflag-feedback-dialog-primary-button-color, #1e1f24);
color: var(--reflag-feedback-dialog-primary-button-color, white);
border: 1px solid
var(--reflag-feedback-dialog-primary-border-color, #d8d9df);
var(--reflag-feedback-dialog-primary-border-color, #09090b);
}

&:disabled {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-sdk/src/feedback/ui/FeedbackDialog.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.dialog {
position: fixed;
width: 210px;
padding: 16px 22px 10px;
padding: 16px 16px 16px;
font-size: var(--reflag-feedback-dialog-font-size, 1rem);
font-family: var(
--reflag-feedback-dialog-font-family,
Expand Down
Loading