mirror of
https://git.coastit.co.za/caitlin/CRM_ENVR.git
synced 2026-07-10 11:15:43 +02:00
Merge branch 'main' of https://git.coastit.co.za/caitlin/CRM_ENVR
This commit is contained in:
@@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { useUser } from "@/providers/user-provider";
|
||||
import { useNotifications } from "@/providers/notification-provider";
|
||||
import { BugReportModal } from "@/components/shared/bug-report-modal";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
LogOut,
|
||||
User,
|
||||
Settings,
|
||||
Bug,
|
||||
CheckCheck,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
@@ -43,6 +45,7 @@ export function Topbar({ onMenuClick }: TopbarProps) {
|
||||
useNotifications();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [bugModalOpen, setBugModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -113,6 +116,17 @@ export function Topbar({ onMenuClick }: TopbarProps) {
|
||||
<Search className="h-5 w-5" />
|
||||
</Button>
|
||||
|
||||
{/* Report a Bug */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setBugModalOpen(true)}
|
||||
className="text-muted-foreground"
|
||||
title="Report a Bug"
|
||||
>
|
||||
<Bug className="h-5 w-5" />
|
||||
</Button>
|
||||
|
||||
{/* Theme toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -247,6 +261,7 @@ export function Topbar({ onMenuClick }: TopbarProps) {
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<BugReportModal open={bugModalOpen} onClose={() => setBugModalOpen(false)} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import { X, Bug, Loader2, CheckCircle } from "lucide-react"
|
||||
|
||||
interface BugReportModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function BugReportModal({ open, onClose }: BugReportModalProps) {
|
||||
const [title, setTitle] = useState("")
|
||||
const [description, setDescription] = useState("")
|
||||
const [severity, setSeverity] = useState("medium")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [error, setError] = useState("")
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError("")
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/bug-reports", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
description,
|
||||
severity,
|
||||
page_url: window.location.href,
|
||||
}),
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setSubmitted(true)
|
||||
setTitle("")
|
||||
setDescription("")
|
||||
setSeverity("medium")
|
||||
} else {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setError(data.error || "Failed to submit bug report.")
|
||||
}
|
||||
} catch {
|
||||
setError("Connection error. Please try again.")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setSubmitted(false)
|
||||
setError("")
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||
className="relative w-full max-w-md rounded-xl border bg-white dark:bg-[#141414] p-6 shadow-2xl"
|
||||
>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-4 top-4 text-[#888888] hover:text-[#111111] dark:hover:text-white transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
{submitted ? (
|
||||
<div className="flex flex-col items-center py-8 text-center">
|
||||
<CheckCircle className="h-12 w-12 text-emerald-500 mb-4" />
|
||||
<h3 className="text-lg font-semibold text-[#111111] dark:text-white mb-2">
|
||||
Bug Report Submitted
|
||||
</h3>
|
||||
<p className="text-sm text-[#666666] dark:text-[#AAAAAA]">
|
||||
Thank you for your report. The development team will investigate.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="mt-6 rounded-lg bg-[#CC0000] px-6 py-2 text-sm font-medium text-white hover:bg-[#AA0000] transition-colors"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-red-100 dark:bg-red-900/30">
|
||||
<Bug className="h-5 w-5 text-[#CC0000]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-[#111111] dark:text-white">
|
||||
Report a Bug
|
||||
</h3>
|
||||
<p className="text-xs text-[#666666] dark:text-[#AAAAAA]">
|
||||
Help us improve the system
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 rounded-lg bg-red-500/10 px-4 py-2 text-sm text-red-500">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-[#444444] dark:text-[#CCCCCC]">
|
||||
Title <span className="text-[#CC0000]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Brief description of the issue"
|
||||
required
|
||||
className="w-full rounded-lg border border-[#E0E0E0] dark:border-[#333333] bg-white dark:bg-[#1E1E1E] px-3 py-2 text-sm text-[#111111] dark:text-white placeholder-[#999999] focus:border-[#CC0000] focus:outline-none focus:ring-1 focus:ring-[#CC0000]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-[#444444] dark:text-[#CCCCCC]">
|
||||
Description <span className="text-[#CC0000]">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="What happened? What did you expect?"
|
||||
rows={4}
|
||||
required
|
||||
className="w-full resize-none rounded-lg border border-[#E0E0E0] dark:border-[#333333] bg-white dark:bg-[#1E1E1E] px-3 py-2 text-sm text-[#111111] dark:text-white placeholder-[#999999] focus:border-[#CC0000] focus:outline-none focus:ring-1 focus:ring-[#CC0000]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-[#444444] dark:text-[#CCCCCC]">
|
||||
Severity
|
||||
</label>
|
||||
<select
|
||||
value={severity}
|
||||
onChange={(e) => setSeverity(e.target.value)}
|
||||
className="w-full rounded-lg border border-[#E0E0E0] dark:border-[#333333] bg-white dark:bg-[#1E1E1E] px-3 py-2 text-sm text-[#111111] dark:text-white focus:border-[#CC0000] focus:outline-none focus:ring-1 focus:ring-[#CC0000]"
|
||||
>
|
||||
<option value="low">Low — Minor cosmetic issue</option>
|
||||
<option value="medium">Medium — Affects functionality</option>
|
||||
<option value="high">High — Major feature broken</option>
|
||||
<option value="critical">Critical — System is blocked</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] px-3 py-2">
|
||||
<p className="text-xs text-[#888888]">
|
||||
<span className="font-medium">Page:</span> {typeof window !== "undefined" ? window.location.href : ""}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg bg-[#CC0000] px-4 py-2.5 text-sm font-medium text-white hover:bg-[#AA0000] disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||||
{loading ? "Submitting..." : "Submit Bug Report"}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user