mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: critical first-login soft-lock in usage survey overlay (#392)
* fix: prevent UsageSurveyOverlay from soft-locking the first-login password-change flow Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * fix: prevent double feedback submission when the settings write fails Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * refactor: consolidate feedback enums into packages/shared as a single source of truth Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * feat: add ARIA semantics, dismiss-button guard, and shared auth-route list to UsageSurveyOverlay Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp * test: cover the submit-failure retry path and a persona-only minimal payload Claude-Session: https://claude.ai/code/session_01KAC9Lbx8AmebAnj9WQZXHp
This commit is contained in:
@@ -1,4 +1,17 @@
|
||||
import { ANALYTICS_BAKED, ANALYTICS_EVENTS, APP_VERSION } from "@snapotter/shared";
|
||||
import {
|
||||
ANALYTICS_BAKED,
|
||||
ANALYTICS_EVENTS,
|
||||
APP_VERSION,
|
||||
type FeedbackErrorCategory,
|
||||
type FeedbackFrictionArea,
|
||||
type FeedbackImportantArea,
|
||||
type FeedbackInstallMethod,
|
||||
type FeedbackSentiment,
|
||||
type FeedbackSource,
|
||||
type FeedbackSurveyId,
|
||||
type FeedbackType,
|
||||
type FeedbackUsageType,
|
||||
} from "@snapotter/shared";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { PostHog } from "posthog-node";
|
||||
import { db, schema } from "../db/index.js";
|
||||
@@ -7,30 +20,12 @@ import { analyticsEnabled, bakedEnabled } from "./analytics-gate.js";
|
||||
|
||||
let posthogClient: PostHog | null = null;
|
||||
|
||||
export const FEEDBACK_SOURCE_VALUES = [
|
||||
"global",
|
||||
"tool_result",
|
||||
"failed_job",
|
||||
"admin_installer",
|
||||
"search_miss",
|
||||
"onboarding",
|
||||
] as const;
|
||||
|
||||
export const FEEDBACK_SURVEY_ID_VALUES = [
|
||||
"global-feedback-v1",
|
||||
"tool-result-v1",
|
||||
"failed-job-v1",
|
||||
"admin-install-v1",
|
||||
"search-miss-v1",
|
||||
"onboarding-usage-v1",
|
||||
] as const;
|
||||
|
||||
export interface FeedbackEventProperties {
|
||||
source: (typeof FEEDBACK_SOURCE_VALUES)[number];
|
||||
survey_id?: (typeof FEEDBACK_SURVEY_ID_VALUES)[number];
|
||||
source: FeedbackSource;
|
||||
survey_id?: FeedbackSurveyId;
|
||||
prompt_variant?: string;
|
||||
sentiment?: "great" | "okay" | "issue" | "missing" | "bug" | "idea" | "other";
|
||||
feedback_type?: "bug" | "feature_request" | "confusing_ux" | "performance" | "other";
|
||||
sentiment?: FeedbackSentiment;
|
||||
feedback_type?: FeedbackType;
|
||||
message?: string;
|
||||
contact_ok: boolean;
|
||||
contact_email?: string;
|
||||
@@ -39,28 +34,11 @@ export interface FeedbackEventProperties {
|
||||
tool_id?: string;
|
||||
search_query?: string;
|
||||
job_status?: "completed" | "failed";
|
||||
install_method?: "docker" | "docker_compose" | "source" | "cloud" | "other";
|
||||
usage_type?: "personal" | "team_internal" | "business_workflow" | "education" | "evaluating";
|
||||
important_areas?: string[];
|
||||
friction_area?:
|
||||
| "smooth"
|
||||
| "docker"
|
||||
| "environment_variables"
|
||||
| "auth"
|
||||
| "storage"
|
||||
| "workers"
|
||||
| "ai_tools"
|
||||
| "docs"
|
||||
| "performance"
|
||||
| "other";
|
||||
error_category?:
|
||||
| "validation_error"
|
||||
| "upload_error"
|
||||
| "processing_error"
|
||||
| "timeout"
|
||||
| "unsupported_format"
|
||||
| "worker_unavailable"
|
||||
| "unknown";
|
||||
install_method?: FeedbackInstallMethod;
|
||||
usage_type?: FeedbackUsageType;
|
||||
important_areas?: FeedbackImportantArea[];
|
||||
friction_area?: FeedbackFrictionArea;
|
||||
error_category?: FeedbackErrorCategory;
|
||||
}
|
||||
|
||||
export async function initAnalytics(): Promise<void> {
|
||||
|
||||
@@ -1,59 +1,20 @@
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
captureFeedback,
|
||||
FEEDBACK_ERROR_CATEGORY_VALUES,
|
||||
FEEDBACK_FRICTION_AREA_VALUES,
|
||||
FEEDBACK_IMPORTANT_AREA_VALUES,
|
||||
FEEDBACK_INSTALL_METHOD_VALUES,
|
||||
FEEDBACK_SENTIMENT_VALUES,
|
||||
FEEDBACK_SOURCE_VALUES,
|
||||
FEEDBACK_SURVEY_ID_VALUES,
|
||||
type FeedbackEventProperties,
|
||||
} from "../lib/analytics.js";
|
||||
FEEDBACK_TYPE_VALUES,
|
||||
FEEDBACK_USAGE_TYPE_VALUES,
|
||||
} from "@snapotter/shared";
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
import { captureFeedback, type FeedbackEventProperties } from "../lib/analytics.js";
|
||||
import { analyticsEnabled } from "../lib/analytics-gate.js";
|
||||
import { requireAuth } from "../plugins/auth.js";
|
||||
|
||||
const SENTIMENT_VALUES = ["great", "okay", "issue", "missing", "bug", "idea", "other"] as const;
|
||||
const FEEDBACK_TYPE_VALUES = [
|
||||
"bug",
|
||||
"feature_request",
|
||||
"confusing_ux",
|
||||
"performance",
|
||||
"other",
|
||||
] as const;
|
||||
const INSTALL_METHOD_VALUES = ["docker", "docker_compose", "source", "cloud", "other"] as const;
|
||||
const USAGE_TYPE_VALUES = [
|
||||
"personal",
|
||||
"team_internal",
|
||||
"business_workflow",
|
||||
"education",
|
||||
"evaluating",
|
||||
] as const;
|
||||
const IMPORTANT_AREA_VALUES = [
|
||||
"images",
|
||||
"pdf_docs",
|
||||
"video_audio",
|
||||
"batch_workflows",
|
||||
"ai_tools",
|
||||
] as const;
|
||||
const FRICTION_AREA_VALUES = [
|
||||
"smooth",
|
||||
"docker",
|
||||
"environment_variables",
|
||||
"auth",
|
||||
"storage",
|
||||
"workers",
|
||||
"ai_tools",
|
||||
"docs",
|
||||
"performance",
|
||||
"other",
|
||||
] as const;
|
||||
const ERROR_CATEGORY_VALUES = [
|
||||
"validation_error",
|
||||
"upload_error",
|
||||
"processing_error",
|
||||
"timeout",
|
||||
"unsupported_format",
|
||||
"worker_unavailable",
|
||||
"unknown",
|
||||
] as const;
|
||||
|
||||
const toolIdSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
@@ -83,7 +44,7 @@ const feedbackBodySchema = z
|
||||
.max(80)
|
||||
.regex(/^[a-z0-9_-]+$/)
|
||||
.optional(),
|
||||
sentiment: z.enum(SENTIMENT_VALUES).optional(),
|
||||
sentiment: z.enum(FEEDBACK_SENTIMENT_VALUES).optional(),
|
||||
feedbackType: z.enum(FEEDBACK_TYPE_VALUES).optional(),
|
||||
message: optionalText(2000),
|
||||
contactOk: z.boolean().default(false),
|
||||
@@ -93,11 +54,11 @@ const feedbackBodySchema = z
|
||||
toolId: toolIdSchema.optional(),
|
||||
searchQuery: optionalText(200),
|
||||
jobStatus: z.enum(["completed", "failed"]).optional(),
|
||||
installMethod: z.enum(INSTALL_METHOD_VALUES).optional(),
|
||||
usageType: z.enum(USAGE_TYPE_VALUES).optional(),
|
||||
importantAreas: z.array(z.enum(IMPORTANT_AREA_VALUES)).max(5).optional(),
|
||||
frictionArea: z.enum(FRICTION_AREA_VALUES).optional(),
|
||||
errorCategory: z.enum(ERROR_CATEGORY_VALUES).optional(),
|
||||
installMethod: z.enum(FEEDBACK_INSTALL_METHOD_VALUES).optional(),
|
||||
usageType: z.enum(FEEDBACK_USAGE_TYPE_VALUES).optional(),
|
||||
importantAreas: z.array(z.enum(FEEDBACK_IMPORTANT_AREA_VALUES)).max(5).optional(),
|
||||
frictionArea: z.enum(FEEDBACK_FRICTION_AREA_VALUES).optional(),
|
||||
errorCategory: z.enum(FEEDBACK_ERROR_CATEGORY_VALUES).optional(),
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
const hasText = Boolean(value.message?.trim());
|
||||
|
||||
Reference in New Issue
Block a user