Skip to content

Feedback System

Overview

MysticX includes a two-channel feedback system that lets users share their experience, report bugs, and suggest improvements. Feedback is collected through a floating widget on all main pages and a dedicated full-page form, then managed by admins in the admin panel.


Feedback Channels

Floating Widget

A lightweight feedback widget that appears on all (main) layout pages after a 5-second delay.

Flow:

  1. User clicks the floating "Feedback" button (bottom-right corner)
  2. A compact panel slides up with the question "How's your experience?"
  3. User picks a mood emoji (Terrible → Bad → Okay → Good → Amazing)
  4. A textarea expands for optional text input (max 500 characters)
  5. User clicks "Send Feedback" to submit

Behavior:

  • Hidden on /feedback and /auth/* paths to avoid redundancy
  • After successful submission, the button hides for 7 days (stored in localStorage under feedback_widget_submitted_at)
  • On mobile: icon-only trigger button; on desktop: icon + localized "Feedback" label
  • Click outside or Escape key closes the panel
  • Non-logged-in users submit with a placeholder email; logged-in users auto-fill their session email
  • Captures window.location.pathname for pageUrl tracking

Files:

  • components/FeedbackWidget/FeedbackWidget.tsx — Main widget component
  • components/FeedbackWidget/MoodPicker.tsx — 5-mood emoji picker (supports size="sm" for widget, size="lg" for page)
  • components/FeedbackWidget/FeedbackWidget.css — Animations
  • stores/feedbackWidgetStore.ts — Zustand store (isOpen, open, close)

Feedback Page

A full-page form at /feedback with richer input options.

Features:

  • Email field (pre-filled from session)
  • Mood picker (optional, large variant) — reuses MoodPicker with size="lg"
  • Content textarea (required, max 2,000 characters)
  • Screenshot upload (up to 5 images, JPEG/PNG/WebP, max 5MB each)
  • FAQ section with common questions
  • Hero section with animated title and gold shimmer effect

Files:

  • app/[locale]/(main)/feedback/page.tsx — Server component with i18n metadata
  • app/[locale]/(main)/feedback/_components/FeedbackPageContent.tsx — Hero + layout
  • app/[locale]/(main)/feedback/_components/FeedbackForm.tsx — Form component
  • app/[locale]/(main)/feedback/_components/FeedbackFAQ.tsx — FAQ accordion

Data Model

prisma
enum FeedbackStatus {
  PENDING
  REVIEWED
  RESOLVED
}

enum FeedbackMood {
  AMAZING
  GOOD
  OKAY
  BAD
  TERRIBLE
}

model UserFeedback {
  id             String          @id @default(uuid())
  userId         String?
  user           User?           @relation(...)
  email          String
  content        String?         @db.Text
  screenshotUrls Json            @default("[]")  // string[], max 5
  status         FeedbackStatus  @default(PENDING)
  adminNote      String?         @db.Text
  mood           FeedbackMood?
  source         String          @default("page") // "widget" | "page"
  pageUrl        String?
  createdAt      DateTime        @default(now())
  updatedAt      DateTime        @updatedAt
}

Key fields:

  • mood — Nullable. Set by widget submissions (required); optional on page submissions.
  • source"widget" or "page". Identifies the submission channel.
  • pageUrl — The page the user was on when submitting. Captured automatically.
  • content — Optional for widget submissions (mood-only is valid); required for page submissions.

API Endpoints

POST /api/v1/feedback

Submits a feedback entry. Auth is optional (guests can submit).

Request body:

json
{
  "email": "user@example.com",
  "content": "Great app!",
  "screenshotUrls": [],
  "mood": "AMAZING",
  "source": "widget",
  "pageUrl": "/en/love-tarot"
}

Validation rules:

FieldWidgetPage
emailRequired (valid format)Required (valid format)
moodRequiredOptional
contentOptionalRequired
screenshotUrlsNot supportedOptional (max 5)
pageUrlAuto-capturedAuto-captured

POST /api/v1/feedback/upload

Uploads a screenshot to Cloudflare R2 for use in the page form.

  • Accepts: FormData with a file field
  • Validates: JPEG/PNG/WebP, max 5MB, verified with sharp
  • Returns: { data: { url } } with the public R2 URL

Admin Panel

Located at /admin/app/feedback.

Feedback List

  • Filterable by status: All, Pending, Reviewed, Resolved
  • Searchable by email or content
  • Paginated (20 per page)
  • Columns: Email, Mood (emoji), Content, Source (badge), Screenshots, Status, Submitted, Actions

Feedback Detail Modal

  • Full content view with metadata (email, user, timestamp)
  • Source badge and mood display
  • Page URL where feedback was submitted
  • Screenshot gallery with external link buttons
  • Status dropdown (Pending → Reviewed → Resolved)
  • Admin note textarea

Admin Actions

  • updateFeedback(id, { status, adminNote }) — Update status and notes
  • deleteFeedback(id) — Delete feedback and clean up R2 screenshots

i18n

All user-facing text is fully translated in 12 locales using inline useTrans/getTrans dictionaries: en, zh_CN, ja, ko, pt, es, fr, de, ar, zh_TW, id, nl.

The admin panel is English-only per project conventions.


A "Feedback" link is included in the SiteFooter under the "Explore" section, pointing to /feedback.

Internal documentation for MysticX team