From f914eaf1c4c0f5900d1ecf31ffc768772c18f15d Mon Sep 17 00:00:00 2001 From: Nick Sorrell Date: Mon, 23 Jun 2025 10:12:59 -0400 Subject: [PATCH 1/2] Adding feedback button and modal --- resources/js/Components/AppSidebar.tsx | 320 +++++++++--------- .../js/Components/GeneralFeedbackModal.tsx | 167 +++++++++ 2 files changed, 336 insertions(+), 151 deletions(-) create mode 100644 resources/js/Components/GeneralFeedbackModal.tsx diff --git a/resources/js/Components/AppSidebar.tsx b/resources/js/Components/AppSidebar.tsx index e39f94e..3d36545 100644 --- a/resources/js/Components/AppSidebar.tsx +++ b/resources/js/Components/AppSidebar.tsx @@ -3,6 +3,7 @@ import { ChevronRight, GalleryVerticalEnd, Home, + MessageCircle, Package, Truck, Users, @@ -11,6 +12,7 @@ import { import * as React from 'react'; import { useEffect, useState } from 'react'; +import GeneralFeedbackModal from '@/Components/GeneralFeedbackModal'; import { NavUser } from '@/Components/NavUser'; import { TeamSwitcher } from '@/Components/TeamSwitcher'; import { @@ -38,6 +40,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { const permissions = usePage().props.auth.permissions; const config = usePage().props.config; const [isOrgMenuOpen, setIsOrgMenuOpen] = useState(false); + const [isFeedbackModalOpen, setIsFeedbackModalOpen] = useState(false); useEffect(() => { const savedState = localStorage.getItem('orgMenuOpen'); @@ -76,204 +79,219 @@ export function AppSidebar({ ...props }: React.ComponentProps) { }; return ( - - - - - - - - - - Dashboard - - - - - - Shipments - - - - - - Carriers - - - - - - Customers - - - - - - Facilities - - - - {(permissions?.ORGANIZATION_MANAGER || - permissions?.ORGANIZATION_MANAGE_USERS) && ( - + + + + + + + - - - - - Organization - - - - - - - - Users - - - - - Roles - - - {permissions?.INTEGRATION_SETTINGS_EDIT && ( + + + Dashboard + + + + + + Shipments + + + + + + Carriers + + + + + + Customers + + + + + + Facilities + + + + {(permissions?.ORGANIZATION_MANAGER || + permissions?.ORGANIZATION_MANAGE_USERS) && ( + + + + + + Organization + + + + + - Integration Settings - - - )} - {permissions?.ORGANIZATION_MANAGER && ( - - - Document Templates + Users - )} - {permissions?.ORGANIZATION_MANAGER && ( - Settings + Roles - )} - {permissions?.ORGANIZATION_BILLING && - config?.enable_billing && ( + {permissions?.INTEGRATION_SETTINGS_EDIT && ( - Billing + Integration Settings )} - - - - - )} - - - - - - - + {permissions?.ORGANIZATION_MANAGER && ( + + + Document Templates + + + )} + {permissions?.ORGANIZATION_MANAGER && ( + + + Settings + + + )} + {permissions?.ORGANIZATION_BILLING && + config?.enable_billing && ( + + + Billing + + + )} + + + + + )} + + setIsFeedbackModalOpen(true)} + > + + Send Feedback + + + + + + + + + + setIsFeedbackModalOpen(false)} + userEmail={user.email} + /> + ); } diff --git a/resources/js/Components/GeneralFeedbackModal.tsx b/resources/js/Components/GeneralFeedbackModal.tsx new file mode 100644 index 0000000..bbf10cd --- /dev/null +++ b/resources/js/Components/GeneralFeedbackModal.tsx @@ -0,0 +1,167 @@ +import { Button } from '@/Components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/Components/ui/dialog'; +import { Label } from '@/Components/ui/label'; +import { Textarea } from '@/Components/ui/textarea'; +import { useToast } from '@/hooks/UseToast'; +import { router } from '@inertiajs/react'; +import { CheckCircle, Send } from 'lucide-react'; +import { useState } from 'react'; + +interface GeneralFeedbackModalProps { + isOpen: boolean; + onClose: () => void; + userEmail: string; +} + +export default function GeneralFeedbackModal({ + isOpen, + onClose, + userEmail, +}: GeneralFeedbackModalProps) { + const [message, setMessage] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [errors, setErrors] = useState<{ feedback?: string }>({}); + const { toast } = useToast(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (isSubmitting) return; + + // Reset errors + setErrors({}); + + // Basic validation + const newErrors: { feedback?: string } = {}; + if (!message) { + newErrors.feedback = 'Message is required'; + } else if (message.length < 10) { + newErrors.feedback = 'Message must be at least 10 characters'; + } + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + setIsSubmitting(true); + + router.post( + route('feedback.submit'), + { + email: userEmail, + feedback: message, + }, + { + onSuccess: () => { + setMessage(''); + setErrors({}); + onClose(); + + // Show success toast + toast({ + title: 'Thank you for your feedback!', + description: ( + <> + + We've received your feedback and will review it + promptly. + + ), + }); + }, + onError: (errors) => { + setErrors(errors); + toast({ + title: 'Submission failed', + description: + 'There was an error submitting your feedback. Please try again.', + variant: 'destructive', + }); + }, + onFinish: () => { + setIsSubmitting(false); + }, + }, + ); + }; + + const handleClose = () => { + if (isSubmitting) return; + setMessage(''); + setErrors({}); + onClose(); + }; + + return ( + + + + Send Feedback + + Share your thoughts, report issues, or ask questions. + We'd love to hear from you! + + + +
+
+
+ +