Skip to content
Merged
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
24 changes: 24 additions & 0 deletions apps/dashboard/src/@/analytics/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,27 @@ export function reportChainInfraRpcOmissionAgreed(properties: {
}) {
posthog.capture("chain infra checkout rpc omission agreed", properties);
}

// ----------------------------
// FEEDBACK
// ----------------------------

/**
* ### Why do we need to report this event?
* - To track user feedback and sentiment about the product
* - To identify common issues or feature requests
* - To measure user satisfaction and engagement
* - To prioritize product improvements based on user input
*
* ### Who is responsible for this event?
* @gisellechacon
*/
export function reportProductFeedback(properties: {
feedback: string;
source: "desktop" | "mobile";
}) {
posthog.capture("product feedback submitted", {
feedback: properties.feedback,
source: properties.source,
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use client";

import Link from "next/link";
import type React from "react";
import { useState } from "react";
import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import { reportProductFeedback } from "@/analytics/report";
import { NotificationsButton } from "@/components/notifications/notification-button";
import { NavLink } from "@/components/ui/NavLink";
import type { Account } from "@/hooks/useApi";
import { AccountButton } from "./account-button.client";
import { ResourcesDropdownButton } from "./ResourcesDropdownButton";
Expand Down Expand Up @@ -31,6 +37,32 @@ export function SecondaryNav(props: {
}

export function SecondaryNavLinks() {
const [showFeedbackDropdown, setShowFeedbackDropdown] = useState(false);
const [modalFeedback, setModalFeedback] = useState("");

const handleModalSubmit = (e?: React.FormEvent) => {
e?.preventDefault();

// Report feedback to PostHog
reportProductFeedback({
feedback: modalFeedback,
source: "desktop",
});

// Show success notification
toast.success("Feedback submitted successfully!", {
description: "Thank you for your feedback. We'll review it shortly.",
});

setModalFeedback("");
setShowFeedbackDropdown(false);
};

const handleModalCancel = () => {
setModalFeedback("");
setShowFeedbackDropdown(false);
};

return (
<div className="flex items-center gap-6">
<ResourcesDropdownButton />
Expand All @@ -44,14 +76,77 @@ export function SecondaryNavLinks() {
Docs
</Link>

<Link
className="text-muted-foreground text-sm hover:text-foreground"
href="https://feedback.thirdweb.com"
rel="noopener noreferrer"
target="_blank"
>
Feedback
</Link>
<div className="relative">
<button
type="button"
onClick={() => setShowFeedbackDropdown(!showFeedbackDropdown)}
className="text-muted-foreground text-sm hover:text-foreground border border-border px-3 py-1.5 rounded-full hover:bg-muted transition-colors"
>
Feedback
</button>

{showFeedbackDropdown && (
<div
id="feedback-dropdown"
role="dialog"
aria-labelledby="feedback-heading"
className="absolute top-full right-0 mt-2 bg-background border border-border rounded-2xl p-3 w-96 z-50"
>
<h2
id="feedback-heading"
className="text-foreground text-base font-sans mb-2"
>
Share your feedback with us:
</h2>
<form onSubmit={handleModalSubmit} className="contents">
<label htmlFor="feedback-text" className="sr-only">
Feedback
</label>
<textarea
id="feedback-text"
value={modalFeedback}
onChange={(e) => setModalFeedback(e.target.value)}
maxLength={1000}
aria-describedby="feedback-help"
className="w-full bg-background text-foreground rounded-lg p-4 min-h-[120px] resize-none border border-border focus:border-border focus:outline-none placeholder-muted-foreground font-sans mb-4 text-sm"
placeholder="Tell us what you think..."
/>

<div className="flex items-start justify-between gap-4">
<div className="text-muted-foreground text-xs font-sans">
<div>Have a technical issue?</div>
<div>
<NavLink
href="/team/~/support"
className="underline hover:text-foreground transition-colors"
>
Contact support
</NavLink>
.
</div>
</div>
<div className="flex gap-2 flex-shrink-0">
<button
type="button"
onClick={handleModalCancel}
className="bg-transparent text-foreground px-4 py-1.5 rounded-full font-sans text-sm border border-border hover:bg-muted transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={!modalFeedback.trim()}
aria-disabled={!modalFeedback.trim()}
className="bg-primary text-primary-foreground px-4 py-1.5 rounded-full font-sans text-sm hover:bg-primary/90 transition-colors disabled:opacity-50"
>
Submit
</button>
</div>
</div>
</form>
</div>
)}
</div>
</div>
);
}
112 changes: 103 additions & 9 deletions apps/dashboard/src/app/(app)/components/MobileBurgerMenuButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import {
ChevronDownIcon,
ChevronUpIcon,
LogOutIcon,
MenuIcon,
MoonIcon,
Expand All @@ -12,8 +14,11 @@ import {
import Link from "next/link";
import { useTheme } from "next-themes";
import { useLayoutEffect, useState } from "react";
import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import { reportProductFeedback } from "@/analytics/report";
import { Button } from "@/components/ui/button";
import { NavLink } from "@/components/ui/NavLink";
import { Separator } from "@/components/ui/separator";
import { SkeletonContainer } from "@/components/ui/skeleton";
import { useEns } from "@/hooks/contract-hooks";
Expand All @@ -36,6 +41,8 @@ export function MobileBurgerMenuButton(
},
) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showFeedbackSection, setShowFeedbackSection] = useState(false);
const [modalFeedback, setModalFeedback] = useState("");
const { setTheme, theme } = useTheme();
const ensQuery = useEns({
addressOrEnsName:
Expand All @@ -44,6 +51,29 @@ export function MobileBurgerMenuButton(
});
// const [isCMDSearchModalOpen, setIsCMDSearchModalOpen] = useState(false);

const handleModalSubmit = (e?: React.FormEvent) => {
e?.preventDefault();

// Report feedback to PostHog
reportProductFeedback({
feedback: modalFeedback,
source: "mobile",
});

// Show success notification
toast.success("Feedback submitted successfully!", {
description: "Thank you for your feedback. We'll review it shortly.",
});

setModalFeedback("");
setShowFeedbackSection(false);
};

const handleModalCancel = () => {
setModalFeedback("");
setShowFeedbackSection(false);
};

useLayoutEffect(() => {
if (isMenuOpen) {
document.body.style.overflow = "hidden";
Expand Down Expand Up @@ -71,7 +101,7 @@ export function MobileBurgerMenuButton(
</Button>

{isMenuOpen && (
<div className="fade-in-0 fixed inset-0 z-50 flex animate-in flex-col bg-background p-6 duration-200">
<div className="fade-in-0 fixed inset-0 z-50 flex animate-in flex-col bg-background p-6 duration-200 overflow-y-auto">
<Button
className="!h-auto absolute top-4 right-4 p-1"
onClick={() => setIsMenuOpen(false)}
Expand Down Expand Up @@ -191,14 +221,78 @@ export function MobileBurgerMenuButton(
Docs
</Link>

<Link
className="text-muted-foreground hover:text-foreground"
href="https://feedback.thirdweb.com"
rel="noopener noreferrer"
target="_blank"
>
Feedback
</Link>
<div className="flex flex-col gap-3">
<button
type="button"
className="flex items-center justify-between text-muted-foreground hover:text-foreground"
onClick={() => setShowFeedbackSection(!showFeedbackSection)}
>
<span>Feedback</span>
{showFeedbackSection ? (
<ChevronUpIcon className="size-4" />
) : (
<ChevronDownIcon className="size-4" />
)}
</button>

{showFeedbackSection && (
<div className="pl-0 pr-4 space-y-4 mb-6">
<h3
id="mobile-feedback-heading"
className="text-sm font-medium text-foreground mb-2"
>
Share your feedback with us:
</h3>
<form onSubmit={handleModalSubmit} className="contents">
<label htmlFor="mobile-feedback-text" className="sr-only">
Feedback
</label>
<textarea
id="mobile-feedback-text"
value={modalFeedback}
onChange={(e) => setModalFeedback(e.target.value)}
maxLength={1000}
aria-describedby="mobile-feedback-help"
className="w-full bg-background text-foreground rounded-lg p-3 min-h-[100px] resize-none border border-border focus:border-border focus:outline-none placeholder-muted-foreground font-sans text-sm"
placeholder="Tell us what you think..."
/>

<div className="flex flex-col gap-3">
<p
id="mobile-feedback-help"
className="text-muted-foreground text-xs"
>
Have a technical issue?{" "}
<NavLink
href="/team/~/support"
className="underline hover:text-foreground transition-colors"
>
Contact support
</NavLink>
.
</p>
<div className="flex gap-3">
<button
type="button"
onClick={handleModalCancel}
className="flex-1 bg-transparent text-foreground px-3 py-2 rounded-full font-sans text-sm border border-border hover:bg-muted transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={!modalFeedback.trim()}
aria-disabled={!modalFeedback.trim()}
className="flex-1 bg-primary text-primary-foreground px-3 py-2 rounded-full font-sans text-sm hover:bg-primary/90 transition-colors disabled:opacity-50"
>
Submit
</button>
</div>
</div>
</form>
</div>
)}
</div>
</div>

<div className="h-6" />
Expand Down
Loading