-
-
Notifications
You must be signed in to change notification settings - Fork 103
feat: add widget feedback feature #2686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
f6e3859
6f832da
d163bfc
76a19dc
6997517
f5c85a0
980f9de
a086b26
cacf86e
11cca5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./widget-feedback-modal"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
type UmamiEventData = Record<string, string | number | boolean>; | ||
|
||
declare global { | ||
interface Window { | ||
umami?: { | ||
track: (eventName: string, eventData?: UmamiEventData) => void; | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,110 @@ | ||||||||||||
"use client"; | ||||||||||||
|
||||||||||||
import { useState } from "react"; | ||||||||||||
import { Button, Group, Rating, Stack, Textarea, TextInput } from "@mantine/core"; | ||||||||||||
import { useDisclosure } from "@mantine/hooks"; | ||||||||||||
|
||||||||||||
import { createModal } from "@homarr/modals"; | ||||||||||||
import { showSuccessNotification } from "@homarr/notifications"; | ||||||||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client"; | ||||||||||||
|
||||||||||||
interface WidgetFeedbackModalProps { | ||||||||||||
itemId: string; | ||||||||||||
widgetKind: string; | ||||||||||||
} | ||||||||||||
|
||||||||||||
export const WidgetFeedbackModal = createModal<WidgetFeedbackModalProps>(({ actions, innerProps }) => { | ||||||||||||
const tCommon = useI18n(); | ||||||||||||
const t = useScopedI18n("feedback"); | ||||||||||||
const [usabilityRating, setUsabilityRating] = useState(0); | ||||||||||||
const [featuresRating, setFeaturesRating] = useState(0); | ||||||||||||
const [overallRating, setOverallRating] = useState(0); | ||||||||||||
const [discordUsername, setDiscordUsername] = useState(""); | ||||||||||||
ajnart marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
const [feedbackText, setFeedbackText] = useState(""); | ||||||||||||
const [submitting, handle] = useDisclosure(false); | ||||||||||||
|
||||||||||||
const isFormValid = usabilityRating > 0 && featuresRating > 0 && overallRating > 0; | ||||||||||||
|
||||||||||||
const handleSubmit = () => { | ||||||||||||
handle.open(); | ||||||||||||
|
||||||||||||
try { | ||||||||||||
if (typeof window !== "undefined") { | ||||||||||||
void umami.track("widget-feedback", { | ||||||||||||
itemId: innerProps.itemId, | ||||||||||||
widgetKind: innerProps.widgetKind, | ||||||||||||
usabilityRating, | ||||||||||||
featuresRating, | ||||||||||||
overallRating, | ||||||||||||
discordUsername: discordUsername || "anonymous", | ||||||||||||
feedbackText, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
|
||||||||||||
showSuccessNotification({ | ||||||||||||
title: t("notification.success.title"), | ||||||||||||
message: t("notification.success.message"), | ||||||||||||
}); | ||||||||||||
|
||||||||||||
actions.closeModal(); | ||||||||||||
} catch (error) { | ||||||||||||
console.error("Error sending feedback:", error); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider notifying the user of a submission error (for example by showing an error notification) instead of only logging the error to the console.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually not a bad idea |
||||||||||||
} finally { | ||||||||||||
handle.close(); | ||||||||||||
} | ||||||||||||
}; | ||||||||||||
|
||||||||||||
return ( | ||||||||||||
<Stack> | ||||||||||||
<Stack gap="xs"> | ||||||||||||
<Group> | ||||||||||||
<Stack gap={5} style={{ flex: 1 }}> | ||||||||||||
<label htmlFor="usability-rating">{t("rating.usability.label")}</label> | ||||||||||||
<Rating id="usability-rating" value={usabilityRating} onChange={setUsabilityRating} size="lg" /> | ||||||||||||
</Stack> | ||||||||||||
</Group> | ||||||||||||
|
||||||||||||
<Group> | ||||||||||||
<Stack gap={5} style={{ flex: 1 }}> | ||||||||||||
<label htmlFor="features-rating">{t("rating.features.label")}</label> | ||||||||||||
<Rating id="features-rating" value={featuresRating} onChange={setFeaturesRating} size="lg" /> | ||||||||||||
</Stack> | ||||||||||||
</Group> | ||||||||||||
|
||||||||||||
<Group> | ||||||||||||
<Stack gap={5} style={{ flex: 1 }}> | ||||||||||||
<label htmlFor="overall-rating">{t("rating.overall.label")}</label> | ||||||||||||
<Rating id="overall-rating" value={overallRating} onChange={setOverallRating} size="lg" /> | ||||||||||||
</Stack> | ||||||||||||
</Group> | ||||||||||||
</Stack> | ||||||||||||
|
||||||||||||
<TextInput | ||||||||||||
label={t("discord.label")} | ||||||||||||
placeholder={t("discord.placeholder")} | ||||||||||||
description={t("discord.description")} | ||||||||||||
value={discordUsername} | ||||||||||||
onChange={(event) => setDiscordUsername(event.currentTarget.value)} | ||||||||||||
/> | ||||||||||||
|
||||||||||||
<Textarea | ||||||||||||
label={t("comments.label")} | ||||||||||||
placeholder={t("comments.placeholder")} | ||||||||||||
minRows={4} | ||||||||||||
value={feedbackText} | ||||||||||||
onChange={(event) => setFeedbackText(event.currentTarget.value)} | ||||||||||||
/> | ||||||||||||
|
||||||||||||
<Group justify="flex-end"> | ||||||||||||
<Button variant="default" onClick={() => actions.closeModal()}> | ||||||||||||
{tCommon("common.action.cancel")} | ||||||||||||
</Button> | ||||||||||||
<Button loading={submitting} onClick={handleSubmit} disabled={!isFormValid}> | ||||||||||||
{t("button.submit")} | ||||||||||||
</Button> | ||||||||||||
</Group> | ||||||||||||
</Stack> | ||||||||||||
); | ||||||||||||
}).withOptions({ | ||||||||||||
defaultTitle: (t) => t("feedback.modal.title"), | ||||||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
export * from "./apps"; | ||
export * from "./boards"; | ||
export * from "./invites"; | ||
export * from "./docker"; | ||
export * from "./feedback"; | ||
export * from "./groups"; | ||
export * from "./invites"; | ||
export * from "./search-engines"; | ||
export * from "./docker"; | ||
export * from "./apps"; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Render this conditionally, only when analytics are enabled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this is not analytics. We are just leveraging our umami endpoint to send feedback. It won't send anything else other than that form
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... in theory you're right, but we disable umami when you disabled analytics.
For the sake of transparency and integrity, I think we should keep doing it that way, hence disabling the feedback menu item.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Meierschlumpf @SeDemal what's your opinion on this ?