Include optional feedback sentiment and direct support contact

This commit is contained in:
webadderall committed 2026-11-06 21:07:47 +11:00
1 parent ee4876bd18
commit cdab2fbdd0
13 files changed
+118 -14

No files matched your search

+22 -2
View File
@@ -215,7 +215,9 @@ export function FeedbackDialog({
aria-label={t(`common.feedback.${item.value}`, item.label)}
aria-pressed={sentiment === item.value}
isDisabled={sending}
onPress={() => setSentiment(item.value)}
onPress={() =>
setSentiment(sentiment === item.value ? null : item.value)
}
>
<span aria-hidden="true">{item.emoji}</span>
</Button>
@@ -305,7 +307,7 @@ export function FeedbackDialog({
<Description className="text-xs">
{t(
"common.feedback.diagnostics",
"Relevant diagnostic logs will be attached.",
"Diagnostic logs attached. Feedback sent to support@recordly.dev",
)}
</Description>
{error && (
@@ -320,6 +322,24 @@ export function FeedbackDialog({
</Button>
</Form>
)}
{!sent && (
<p className="text-center text-xs text-muted">
{t("common.feedback.orEmail", "Or email")}{" "}
<a
href="mailto:support@recordly.dev"
className="underline underline-offset-4"
onClick={(event) => {
if (!window.electronAPI?.openExternalUrl) return;
event.preventDefault();
void window.electronAPI.openExternalUrl(
"mailto:support@recordly.dev",
);
}}
>
support@recordly.dev
</a>
</p>
)}
</DialogContent>
</Dialog>
);
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Danke für dein Feedback",
"signInRequired": "Melde dich an, um Feedback zu senden.",
"openHome": "Öffne die Startseite, um dich anzumelden.",
"orEmail": "Oder per E-Mail an",
"subject": "Betreff",
"subjectPlaceholder": "Was möchtest du uns mitteilen?",
"description": "Beschreibung",
"descriptionPlaceholder": "Wir lesen jedes Feedback...",
"attachFiles": "Dateien anhängen",
"removeFile": "{{name}} entfernen",
"diagnostics": "Relevante Diagnoseprotokolle werden angehängt.",
"diagnostics": "Diagnoseprotokolle angehängt. Feedback an support@recordly.dev gesendet",
"sending": "Wird gesendet…",
"errors": {
"unavailable": "Feedback ist erst verfügbar, wenn die Kontodienste eingerichtet sind.",
+2 -1
View File
@@ -36,6 +36,7 @@
"thanks": "Thanks for your feedback",
"signInRequired": "Sign in to send feedback.",
"openHome": "Open Home to sign in.",
"orEmail": "Or email",
"missingMessage": "Tell us more before sending.",
"sentiment": "How do you feel?",
"loving": "Loving",
@@ -47,7 +48,7 @@
"descriptionPlaceholder": "We read all feedback...",
"attachFiles": "Attach files",
"removeFile": "Remove {{name}}",
"diagnostics": "Relevant diagnostic logs will be attached.",
"diagnostics": "Diagnostic logs attached. Feedback sent to support@recordly.dev",
"sending": "Sending…",
"errors": {
"unavailable": "Feedback is unavailable until account services are configured.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Gracias por tus comentarios",
"signInRequired": "Inicia sesión para enviar comentarios.",
"openHome": "Abre Inicio para iniciar sesión.",
"orEmail": "O escribe a",
"subject": "Asunto",
"subjectPlaceholder": "¿Qué tienes en mente?",
"description": "Descripción",
"descriptionPlaceholder": "Leemos todos los comentarios...",
"attachFiles": "Adjuntar archivos",
"removeFile": "Eliminar {{name}}",
"diagnostics": "Se adjuntarán los registros de diagnóstico pertinentes.",
"diagnostics": "Registros de diagnóstico adjuntos. Comentarios enviados a support@recordly.dev",
"sending": "Enviando…",
"errors": {
"unavailable": "Los comentarios no están disponibles hasta que se configuren los servicios de cuenta.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Merci pour vos commentaires",
"signInRequired": "Connectez-vous pour envoyer des commentaires.",
"openHome": "Ouvrez l’accueil pour vous connecter.",
"orEmail": "Ou écrivez à",
"subject": "Objet",
"subjectPlaceholder": "Que souhaitez-vous nous dire ?",
"description": "Description",
"descriptionPlaceholder": "Nous lisons tous les commentaires...",
"attachFiles": "Joindre des fichiers",
"removeFile": "Retirer {{name}}",
"diagnostics": "Les journaux de diagnostic pertinents seront joints.",
"diagnostics": "Journaux de diagnostic joints. Commentaires envoyés à support@recordly.dev",
"sending": "Envoi…",
"errors": {
"unavailable": "Les commentaires seront disponibles une fois les services de compte configurés.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Grazie per i tuoi commenti",
"signInRequired": "Accedi per inviare commenti.",
"openHome": "Apri Home per accedere.",
"orEmail": "Oppure scrivi a",
"subject": "Oggetto",
"subjectPlaceholder": "Cosa vorresti dirci?",
"description": "Descrizione",
"descriptionPlaceholder": "Leggiamo tutti i commenti...",
"attachFiles": "Allega file",
"removeFile": "Rimuovi {{name}}",
"diagnostics": "Verranno allegati i registri diagnostici pertinenti.",
"diagnostics": "Registri diagnostici allegati. Feedback inviato a support@recordly.dev",
"sending": "Invio…",
"errors": {
"unavailable": "I commenti saranno disponibili dopo la configurazione dei servizi dell’account.",
+2 -1
View File
@@ -36,6 +36,7 @@
"thanks": "フィードバックありがとうございます",
"signInRequired": "フィードバックを送信するにはログインしてください。",
"openHome": "ホームを開いてログインしてください。",
"orEmail": "またはメール",
"missingMessage": "送信する前に内容を入力してください。",
"sentiment": "使い心地はいかがですか?",
"loving": "満足",
@@ -47,7 +48,7 @@
"descriptionPlaceholder": "すべてのフィードバックに目を通しています…",
"attachFiles": "ファイルを添付",
"removeFile": "{{name}}を削除",
"diagnostics": "関連する診断ログが添付されます。",
"diagnostics": "診断ログを添付。フィードバックは support@recordly.dev に送信されます",
"sending": "送信中…",
"errors": {
"unavailable": "アカウントサービスが設定されるまで、フィードバックは利用できません。",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "피드백을 보내 주셔서 감사합니다",
"signInRequired": "피드백을 보내려면 로그인하세요.",
"openHome": "홈을 열어 로그인하세요.",
"orEmail": "또는 이메일",
"subject": "제목",
"subjectPlaceholder": "어떤 의견이 있으신가요?",
"description": "설명",
"descriptionPlaceholder": "모든 피드백을 읽고 있습니다...",
"attachFiles": "파일 첨부",
"removeFile": "{{name}} 제거",
"diagnostics": "관련 진단 로그가 첨부됩니다.",
"diagnostics": "진단 로그가 첨부되었습니다. 피드백이 support@recordly.dev로 전송됩니다",
"sending": "전송 중…",
"errors": {
"unavailable": "계정 서비스가 구성되어야 피드백을 보낼 수 있습니다.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Bedankt voor je feedback",
"signInRequired": "Meld je aan om feedback te verzenden.",
"openHome": "Open Start om je aan te melden.",
"orEmail": "Of mail naar",
"subject": "Onderwerp",
"subjectPlaceholder": "Wat wil je ons vertellen?",
"description": "Beschrijving",
"descriptionPlaceholder": "We lezen alle feedback...",
"attachFiles": "Bestanden bijvoegen",
"removeFile": "{{name}} verwijderen",
"diagnostics": "Relevante diagnostische logboeken worden bijgevoegd.",
"diagnostics": "Diagnostische logboeken bijgevoegd. Feedback verzonden naar support@recordly.dev",
"sending": "Verzenden…",
"errors": {
"unavailable": "Feedback is pas beschikbaar nadat de accountdiensten zijn ingesteld.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "Obrigado pelos comentários",
"signInRequired": "Entre para enviar comentários.",
"openHome": "Abra a página inicial para entrar.",
"orEmail": "Ou envie um e-mail para",
"subject": "Assunto",
"subjectPlaceholder": "O que você gostaria de compartilhar?",
"description": "Descrição",
"descriptionPlaceholder": "Lemos todos os comentários...",
"attachFiles": "Anexar arquivos",
"removeFile": "Remover {{name}}",
"diagnostics": "Os registros de diagnóstico relevantes serão anexados.",
"diagnostics": "Registros de diagnóstico anexados. Comentários enviados para support@recordly.dev",
"sending": "Enviando…",
"errors": {
"unavailable": "O envio de comentários ficará disponível após a configuração dos serviços da conta.",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "感谢你的反馈",
"signInRequired": "登录以发送反馈。",
"openHome": "打开首页以登录。",
"orEmail": "或发送邮件至",
"subject": "主题",
"subjectPlaceholder": "你有什么想法?",
"description": "说明",
"descriptionPlaceholder": "我们会阅读所有反馈...",
"attachFiles": "添加附件",
"removeFile": "移除 {{name}}",
"diagnostics": "将附上相关诊断日志。",
"diagnostics": "已附上诊断日志。反馈发送至 support@recordly.dev",
"sending": "正在发送…",
"errors": {
"unavailable": "配置账户服务后才能使用反馈功能。",
+2 -1
View File
@@ -36,13 +36,14 @@
"thanks": "感謝你的意見回饋",
"signInRequired": "登入以傳送意見回饋。",
"openHome": "開啟首頁以登入。",
"orEmail": "或寄送郵件至",
"subject": "主旨",
"subjectPlaceholder": "你有什麼想法?",
"description": "說明",
"descriptionPlaceholder": "我們會閱讀所有意見回饋...",
"attachFiles": "附加檔案",
"removeFile": "移除 {{name}}",
"diagnostics": "將附上相關診斷記錄。",
"diagnostics": "已附上診斷記錄。意見回饋傳送至 support@recordly.dev",
"sending": "正在傳送…",
"errors": {
"unavailable": "設定帳號服務後才能使用意見回饋功能。",
+74 -1
View File
@@ -1,6 +1,79 @@
import { expect, test } from "@playwright/test";
import { installDesktopBridge } from "./bridge";
for (const clearSelection of [false, true]) {
test(`feedback submits without an emoji${clearSelection ? " after clearing a selection" : ""}`, async ({
page,
}) => {
await installDesktopBridge(page);
let requestBody = "";
await page.route("**/functions/v1/submit-feedback", async (route) => {
requestBody = route.request().postData() ?? "";
await route.fulfill({ json: { success: true } });
});
await page.goto("/?windowType=editor");
await page.getByRole("button", { name: "Recordly account", exact: true }).click();
await page
.getByRole("dialog", { name: "Account menu", exact: true })
.getByRole("button", { name: "Feedback", exact: true })
.click();
const modal = page.getByRole("dialog", { name: "Send feedback", exact: true });
if (clearSelection) {
const loving = modal.getByRole("button", { name: "Loving", exact: true });
await loving.click();
await expect(loving).toHaveAttribute("aria-pressed", "true");
await loving.click();
}
await expect(modal.locator('[aria-pressed="true"]')).toHaveCount(0);
await modal.getByRole("button", { name: "Send feedback", exact: true }).click();
await expect(modal).toBeVisible();
expect(requestBody).toBe("");
await modal
.getByLabel("Description", { exact: true })
.fill("Please add more keyboard shortcuts.");
await modal.getByRole("button", { name: "Send feedback", exact: true }).click();
await expect(
page.getByRole("dialog", { name: "Thanks for your feedback", exact: true }),
).toBeVisible();
expect(requestBody).toContain('name="title"\r\n\r\nPlease add more keyboard shortcuts.');
expect(requestBody).not.toMatch(/🥰|😐|😠/u);
});
}
for (const signedIn of [false, true]) {
test(`feedback offers support email when signed ${signedIn ? "in" : "out"}`, async ({
page,
}) => {
await installDesktopBridge(page, undefined, { signedIn });
await page.addInitScript(() => {
window.electronAPI.openExternalUrl = async (url: string) => {
document.documentElement.dataset.openedExternalUrl = url;
return { success: true };
};
});
await page.goto("/?windowType=editor");
if (signedIn) {
await page.getByRole("button", { name: "Recordly account", exact: true }).click();
await page
.getByRole("dialog", { name: "Account menu", exact: true })
.getByRole("button", { name: "Feedback", exact: true })
.click();
} else {
await page.getByRole("button", { name: "Feedback", exact: true }).click();
}
const modal = page.getByRole("dialog", { name: "Send feedback", exact: true });
await expect(modal.getByText("Or email", { exact: false })).toBeVisible();
const email = modal.getByRole("link", { name: "support@recordly.dev", exact: true });
await expect(email).toHaveAttribute("href", "mailto:support@recordly.dev");
await email.click();
await expect(page.locator("html")).toHaveAttribute(
"data-opened-external-url",
"mailto:support@recordly.dev",
);
await expect(modal).toBeVisible();
});
}
test("feedback preserves a draft on failure, supports attachments, and includes diagnostics automatically", async ({
page,
}) => {
@@ -31,7 +104,7 @@ test("feedback preserves a draft on failure, supports attachments, and includes
await expect(modal.getByText("notes.txt", { exact: true })).toBeVisible();
await modal.getByRole("button", { name: "Remove notes.txt" }).click();
await expect(modal.getByText("notes.txt", { exact: true })).toHaveCount(0);
await expect(modal.getByText("Relevant diagnostic logs will be attached.")).toBeVisible();
await expect(modal.getByText("Diagnostic logs attached. Feedback sent to support@recordly.dev")).toBeVisible();
await expect(modal.getByRole("checkbox")).toHaveCount(0);
await expect(modal.getByLabel("Title", { exact: true })).toHaveCount(0);
await modal.getByRole("button", { name: "Send feedback", exact: true }).click();