diff --git a/public/content/report-a-bug.md b/public/content/report-a-bug.md index 1411f5eb..4a6a46b2 100644 --- a/public/content/report-a-bug.md +++ b/public/content/report-a-bug.md @@ -1,22 +1,24 @@ --- title: Report a Bug -deck: Found something broken or have a suggestion? Let us know. +deck: Found something broken or have a suggestion? Use the in-app reporter. --- ## How to report an issue -If you've encountered an error, unexpected behavior, or have a suggestion for improvement, we want to know about it! Your feedback is essential to help us improve Ocotillo. Please email us with bug reports or feature requests at [ocotillo-nmbg@nmt.edu](mailto:ocotillo-nmbg@nmt.edu). +Use the **Get Help** button in the top right corner of any page. It opens a panel where you can: -## What to include +- **Report a Bug** — describe what went wrong, and it gets filed directly as a JIRA ticket +- **Request a Feature** — describe a problem and what you'd like Ocotillo to do differently -A good bug report includes: +Your name, email, current page, and browser are captured automatically so you don't have to include them. + +## What makes a good bug report - **What you expected to happen** - **What actually happened** -- **Steps to reproduce** -- the more specific the better -- **Browser and operating system** (e.g. Chrome on macOS) -- **Screenshots are extremely helpful** if the issue is visual +- **Steps to reproduce** — the more specific the better +- **Screenshots** are extremely helpful if the issue is visual ## Contact -For urgent issues or questions, reach out directly to the development team at [ocotillo-nmbg@nmt.edu](mailto:ocotillo-nmbg@nmt.edu). \ No newline at end of file +For urgent issues, email the development team directly at [ocotillo-nmbg@nmt.edu](mailto:ocotillo-nmbg@nmt.edu). diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index a0ede246..7d8474c4 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -43,11 +43,11 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { + Bug, Check, ChevronDown, ChevronRight, FlaskConical, - LifeBuoy, Lock, LogOut, Menu, @@ -63,6 +63,8 @@ import { ReportBugButton } from '@/components/Button' import { AmpRole, PRIMARY_NAV, RESOURCE_NAV, type NavItem } from '@/config/navigation' import { useAccessCapabilities } from '@/hooks' import { useSearch } from '@/providers/search-provider' +import { Textarea } from '@/components/ui/textarea' +import { Label } from '@/components/ui/label' // Support panel state shared between the sidebar footer button and the panel itself export const SupportPanelContext = createContext<{ @@ -351,8 +353,7 @@ function AppSidebar() { )} - {/* Help & Support button — hidden until panel content is defined */} - {/* */} + ) @@ -405,34 +406,84 @@ function SupportPanelTrigger({ collapsed }: { collapsed: boolean }) {
) } const PANEL_MIN_WIDTH = 320 -const PANEL_DEFAULT_WIDTH = 320 +const PANEL_DEFAULT_WIDTH = 360 + +type PanelView = 'home' | 'bug' | 'feature' +type SubmitState = 'idle' | 'loading' | 'success' | 'error' + +interface BugFormData { + whatHappened: string + severity: string +} + +interface FeatureFormData { + problem: string + whoWouldUse: string + whatItShouldDo: string +} + +function getBrowser(): string { + const ua = navigator.userAgent + if (ua.includes('Chrome') && !ua.includes('Edg')) return 'Chrome' + if (ua.includes('Firefox')) return 'Firefox' + if (ua.includes('Safari') && !ua.includes('Chrome')) return 'Safari' + if (ua.includes('Edg')) return 'Edge' + return 'Unknown' +} function SupportPanel() { const { isOpen, close } = useContext(SupportPanelContext) + const { data: user } = useGetIdentity<{ name: string; email: string }>() + const location = useLocation() + + const [view, setView] = useState('home') + const [submitState, setSubmitState] = useState('idle') + const [ticketKey, setTicketKey] = useState('') + const [ticketUrl, setTicketUrl] = useState('') + const [errorMsg, setErrorMsg] = useState('') + + const [bugForm, setBugForm] = useState({ whatHappened: '', severity: 'Low' }) + const [featureForm, setFeatureForm] = useState({ + problem: '', + whoWouldUse: '', + whatItShouldDo: '', + }) + const [width, setWidth] = useState(PANEL_DEFAULT_WIDTH) const outerRef = useRef(null) const innerRef = useRef(null) const dragState = useRef<{ startX: number; startWidth: number } | null>(null) + // Reset to home view when panel closes + useEffect(() => { + if (!isOpen) { + setTimeout(() => { + setView('home') + setSubmitState('idle') + setBugForm({ whatHappened: '', severity: 'Low' }) + setFeatureForm({ problem: '', whoWouldUse: '', whatItShouldDo: '' }) + }, 300) + } + }, [isOpen]) + const onMouseDown = useCallback((e: React.MouseEvent) => { e.preventDefault() dragState.current = { startX: e.clientX, startWidth: width } - // Disable the CSS transition for the duration of the drag if (outerRef.current) outerRef.current.style.transition = 'none' document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' @@ -444,13 +495,11 @@ function SupportPanel() { const maxWidth = Math.floor(window.innerWidth / 2) const delta = dragState.current.startX - e.clientX const next = Math.min(maxWidth, Math.max(PANEL_MIN_WIDTH, dragState.current.startWidth + delta)) - // Write directly to DOM — no React re-render per frame if (outerRef.current) outerRef.current.style.width = `${next}px` if (innerRef.current) innerRef.current.style.width = `${next}px` } const onMouseUp = () => { if (!dragState.current) return - // Commit final width to React state and restore transition const finalWidth = outerRef.current ? parseInt(outerRef.current.style.width, 10) || PANEL_DEFAULT_WIDTH : PANEL_DEFAULT_WIDTH @@ -468,6 +517,48 @@ function SupportPanel() { } }, []) + const handleSubmit = async (type: 'bug' | 'feature') => { + setSubmitState('loading') + setErrorMsg('') + const payload = { + type, + page_url: window.location.href, + reporter_name: user?.name, + reporter_email: user?.email, + browser: getBrowser(), + ...(type === 'bug' + ? { what_happened: bugForm.whatHappened, severity: bugForm.severity } + : { + problem: featureForm.problem, + who_would_use: featureForm.whoWouldUse, + what_it_should_do: featureForm.whatItShouldDo, + }), + } + try { + const res = await fetch('/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }) + if (!res.ok) throw new Error(`Server error ${res.status}`) + const data = await res.json() + setTicketKey(data.jira_key) + setTicketUrl(data.jira_url) + setSubmitState('success') + setTimeout(() => { + setView('home') + setSubmitState('idle') + setBugForm({ whatHappened: '', severity: 'Low' }) + setFeatureForm({ problem: '', whoWouldUse: '', whatItShouldDo: '' }) + }, 3000) + } catch (err) { + setErrorMsg(err instanceof Error ? err.message : 'Something went wrong. Please try again.') + setSubmitState('error') + } + } + + const pageUrl = location.pathname + return (
- {/* Drag handle — updates DOM directly, no React state during drag */} + {/* Drag handle */}
{/* Panel header */}
- Ocotillo Support +
+ {view !== 'home' && ( + + )} + + {view === 'home' && 'Get Help'} + {view === 'bug' && 'Report a Bug'} + {view === 'feature' && 'Request a Feature'} + +
- {/* Panel body — placeholder until content is defined */} -
- -

Support coming soon

-

- This panel will contain help articles, contact options, and other support resources. -

+ {/* Panel body */} +
+ + {/* Home view */} + {view === 'home' && ( +
+

+ Found something broken or have a suggestion? Let us know. +

+
+ +
+
+ +
+

+ For urgent issues, email{' '} + + ocotillo-nmbg@nmt.edu + +

+
+ )} + + {/* Bug form */} + {view === 'bug' && ( +
+ {/* Auto-captured context */} +
+

Captured automatically

+

+ Page: {pageUrl} +

+ {user?.name && ( +

+ Reported by: {user.name} +

+ )} +

+ Browser: {getBrowser()} +

+

+ Date & time:{' '} + {new Date().toLocaleString(undefined, { + dateStyle: 'medium', + timeStyle: 'short', + })} +

+
+ +
+ +