Repository navigation
dashboard: send product feedback to the team #9032
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
Changes from all commits
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,39 @@ | ||
| "use server"; | ||
| import "server-only"; | ||
|
|
||
| import { getAuthToken } from "@/api/auth-token"; | ||
| import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs"; | ||
|
|
||
| export async function submitProductFeedback(request: { | ||
| feedback: string; | ||
| source: "desktop" | "mobile"; | ||
| }): Promise<{ ok: true } | { ok: false; error: string }> { | ||
| const token = await getAuthToken(); | ||
| if (!token) { | ||
| return { error: "You are not logged in", ok: false }; | ||
| } | ||
|
|
||
| const res = await fetch( | ||
| new URL("/v1/account/feedback", NEXT_PUBLIC_THIRDWEB_API_HOST), | ||
| { | ||
| body: JSON.stringify(request), | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| "Content-Type": "application/json", | ||
| }, | ||
| method: "POST", | ||
| }, | ||
| ); | ||
|
|
||
| if (!res.ok) { | ||
| return { | ||
| error: | ||
| res.status === 429 | ||
| ? "Too many requests, please try again in a minute" | ||
| : "Failed to submit feedback, please try again later", | ||
| ok: false, | ||
| }; | ||
| } | ||
|
|
||
| return { ok: true }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import type React from "react"; | |
| import { useState } from "react"; | ||
| import { toast } from "sonner"; | ||
| import type { ThirdwebClient } from "thirdweb"; | ||
| import { submitProductFeedback } from "@/actions/product-feedback"; | ||
| import { reportProductFeedback } from "@/analytics/report"; | ||
| import { NotificationsButton } from "@/components/notifications/notification-button"; | ||
| import { NavLink } from "@/components/ui/NavLink"; | ||
|
|
@@ -39,8 +40,9 @@ export function SecondaryNav(props: { | |
| export function SecondaryNavLinks() { | ||
| const [showFeedbackDropdown, setShowFeedbackDropdown] = useState(false); | ||
| const [modalFeedback, setModalFeedback] = useState(""); | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const handleModalSubmit = (e?: React.FormEvent) => { | ||
| const handleModalSubmit = async (e?: React.FormEvent) => { | ||
| e?.preventDefault(); | ||
|
|
||
| // Report feedback to PostHog | ||
|
|
@@ -49,6 +51,18 @@ export function SecondaryNavLinks() { | |
| source: "desktop", | ||
| }); | ||
|
|
||
| setIsSubmitting(true); | ||
| const result = await submitProductFeedback({ | ||
| feedback: modalFeedback, | ||
| source: "desktop", | ||
| }); | ||
|
Comment on lines
+55
to
+58
Contributor
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve feedback edited during submission. The textarea remains editable while this request runs. If the user changes the text before the request succeeds, the success path clears the newer draft and closes the form. Disable editing during submission, or clear the draft only when it still matches the submitted feedback. 🤖 Prompt for AI Agents |
||
| setIsSubmitting(false); | ||
|
Comment on lines
+55
to
+59
Contributor
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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,115p' 'apps/dashboard/src/@/actions/product-feedback.ts'
sed -n '37,78p' 'apps/dashboard/src/app/(app)/components/Header/SecondaryNav/SecondaryNav.tsx'Repository: thirdweb-dev/js Length of output: 2175 🏁 Script executed: nl -ba 'apps/dashboard/src/@/actions/product-feedback.ts' | sed -n '1,100p'
nl -ba 'apps/dashboard/src/app/(app)/components/Header/SecondaryNav/SecondaryNav.tsx' | sed -n '35,85p'
rg -n -F -- 'export async function getAuthToken' apps/dashboard/srcRepository: thirdweb-dev/js Length of output: 3180 Reset
Suggested fix--- "a/apps/dashboard/src/app/(app)/components/Header/SecondaryNav/SecondaryNav.tsx"
+++ "b/apps/dashboard/src/app/(app)/components/Header/SecondaryNav/SecondaryNav.tsx"
@@ -51,25 +51,29 @@
source: "desktop",
});
setIsSubmitting(true);
- const result = await submitProductFeedback({
- feedback: modalFeedback,
- source: "desktop",
- });
- setIsSubmitting(false);
-
- if (!result.ok) {
- toast.error(result.error);
- return;
- }
-
- // Show success notification
- toast.success("Feedback submitted successfully!", {
- description: "Thank you for your feedback. We'll review it shortly.",
- });
-
- setModalFeedback("");
- setShowFeedbackDropdown(false);
+ try {
+ const result = await submitProductFeedback({
+ feedback: modalFeedback,
+ source: "desktop",
+ });
+
+ if (!result.ok) {
+ toast.error(result.error);
+ return;
+ }
+
+ toast.success("Feedback submitted successfully!", {
+ description: "Thank you for your feedback. We'll review it shortly.",
+ });
+
+ setModalFeedback("");
+ setShowFeedbackDropdown(false);
+ } catch {
+ toast.error("Failed to submit feedback, please try again later");
+ } finally {
+ setIsSubmitting(false);
+ }
};
const handleModalCancel = () => {🤖 Prompt for AI Agents |
||
|
|
||
| if (!result.ok) { | ||
| toast.error(result.error); | ||
| return; | ||
| } | ||
|
|
||
| // Show success notification | ||
| toast.success("Feedback submitted successfully!", { | ||
| description: "Thank you for your feedback. We'll review it shortly.", | ||
|
|
@@ -135,8 +149,8 @@ export function SecondaryNavLinks() { | |
| </button> | ||
| <button | ||
| type="submit" | ||
| disabled={!modalFeedback.trim()} | ||
| aria-disabled={!modalFeedback.trim()} | ||
| disabled={!modalFeedback.trim() || isSubmitting} | ||
| aria-disabled={!modalFeedback.trim() || isSubmitting} | ||
| 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import { useTheme } from "next-themes"; | |
| import { useLayoutEffect, useState } from "react"; | ||
| import { toast } from "sonner"; | ||
| import type { ThirdwebClient } from "thirdweb"; | ||
| import { submitProductFeedback } from "@/actions/product-feedback"; | ||
| import { reportProductFeedback } from "@/analytics/report"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { NavLink } from "@/components/ui/NavLink"; | ||
|
|
@@ -43,6 +44,7 @@ export function MobileBurgerMenuButton( | |
| const [isMenuOpen, setIsMenuOpen] = useState(false); | ||
| const [showFeedbackSection, setShowFeedbackSection] = useState(false); | ||
| const [modalFeedback, setModalFeedback] = useState(""); | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
| const { setTheme, theme } = useTheme(); | ||
| const ensQuery = useEns({ | ||
| addressOrEnsName: | ||
|
|
@@ -51,7 +53,7 @@ export function MobileBurgerMenuButton( | |
| }); | ||
| // const [isCMDSearchModalOpen, setIsCMDSearchModalOpen] = useState(false); | ||
|
|
||
| const handleModalSubmit = (e?: React.FormEvent) => { | ||
| const handleModalSubmit = async (e?: React.FormEvent) => { | ||
| e?.preventDefault(); | ||
|
|
||
| // Report feedback to PostHog | ||
|
|
@@ -60,6 +62,18 @@ export function MobileBurgerMenuButton( | |
| source: "mobile", | ||
| }); | ||
|
|
||
| setIsSubmitting(true); | ||
| const result = await submitProductFeedback({ | ||
| feedback: modalFeedback, | ||
| source: "mobile", | ||
| }); | ||
|
Comment on lines
+66
to
+69
Contributor
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve feedback edited during mobile submission. The textarea remains editable while this request runs. If the user changes the text before the request succeeds, the success path clears the newer draft and closes the section. Disable editing during submission, or clear the draft only when it still matches the submitted feedback. 🤖 Prompt for AI Agents |
||
| setIsSubmitting(false); | ||
|
Comment on lines
+66
to
+70
Contributor
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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,115p' 'apps/dashboard/src/@/actions/product-feedback.ts'
sed -n '43,93p' 'apps/dashboard/src/app/(app)/components/MobileBurgerMenuButton.tsx'Repository: thirdweb-dev/js Length of output: 2548 🏁 Script executed: rg -n -F --glob '*.ts' --glob '*.tsx' -e 'submitProductFeedback' -e 'function getAuthToken' -e 'const getAuthToken' -e 'export async function getAuthToken' apps/dashboard/src
printf '\n--- auth helper candidates ---\n'
rg -n -F --glob '*.ts' --glob '*.tsx' -e 'getAuthToken' apps/dashboard/src/api apps/dashboard/src/@/api 2>/dev/null || true
printf '\n--- mobile handler with imports ---\n'
sed -n '1,90p' 'apps/dashboard/src/app/(app)/components/MobileBurgerMenuButton.tsx'
printf '\n--- desktop caller candidates ---\n'
rg -l -F --glob '*.ts' --glob '*.tsx' 'submitProductFeedback' apps/dashboard/src | while read -r f; do
echo "--- $f"
sed -n '1,130p' "$f"
doneRepository: thirdweb-dev/js Length of output: 17052 Reset submission state when the mobile action rejects.
Suggested fix--- "a/apps/dashboard/src/app/(app)/components/MobileBurgerMenuButton.tsx"
+++ "b/apps/dashboard/src/app/(app)/components/MobileBurgerMenuButton.tsx"
@@ -62,25 +62,29 @@
source: "mobile",
});
setIsSubmitting(true);
- const result = await submitProductFeedback({
- feedback: modalFeedback,
- source: "mobile",
- });
- setIsSubmitting(false);
+ try {
+ const result = await submitProductFeedback({
+ feedback: modalFeedback,
+ source: "mobile",
+ });
- if (!result.ok) {
- toast.error(result.error);
- return;
- }
+ if (!result.ok) {
+ toast.error(result.error);
+ return;
+ }
- // Show success notification
- toast.success("Feedback submitted successfully!", {
- description: "Thank you for your feedback. We'll review it shortly.",
- });
+ toast.success("Feedback submitted successfully!", {
+ description: "Thank you for your feedback. We'll review it shortly.",
+ });
- setModalFeedback("");
- setShowFeedbackSection(false);
+ setModalFeedback("");
+ setShowFeedbackSection(false);
+ } catch {
+ toast.error("Failed to submit feedback, please try again later");
+ } finally {
+ setIsSubmitting(false);
+ }
};
const handleModalCancel = () => {🤖 Prompt for AI Agents |
||
|
|
||
| if (!result.ok) { | ||
| toast.error(result.error); | ||
| return; | ||
| } | ||
|
|
||
| // Show success notification | ||
| toast.success("Feedback submitted successfully!", { | ||
| description: "Thank you for your feedback. We'll review it shortly.", | ||
|
|
@@ -281,8 +295,10 @@ export function MobileBurgerMenuButton( | |
| </button> | ||
| <button | ||
| type="submit" | ||
| disabled={!modalFeedback.trim()} | ||
| aria-disabled={!modalFeedback.trim()} | ||
| disabled={!modalFeedback.trim() || isSubmitting} | ||
| aria-disabled={ | ||
| !modalFeedback.trim() || isSubmitting | ||
| } | ||
| 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 | ||
|
|
||
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.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Validate feedback before forwarding it to the API.
A signed-in caller can invoke
submitProductFeedbackwith blank feedback, more than 1,000 characters, or an unsupportedsource. The TypeScript type and form controls do not validate Server Action arguments at runtime. Validate both fields here and return a field-specific error before making the authenticated POST. Next.js treats Server Actions as public endpoints with untrusted arguments. (nextjs.org) Based on learnings, Server Actions must validate incoming inputs before processing.🤖 Prompt for AI Agents
Source: Learnings