fix: corrige hydration, notificacoes e melhora visual
- Corrige hydration mismatch no Toaster (sonner) e ChatWidgetProvider - Corrige API de preferencias de notificacao (typePreferences como string) - Melhora visual do Switch (estado ativo em preto) - Adiciona icones em circulos na pagina de notificacoes - Corrige acentuacao em "Obrigatorio" - Corrige centralizacao das estrelas de avaliacao nos e-mails - Aplica Sentence case nos titulos dos templates de e-mail - Adiciona script de teste de e-mail 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7a3791117b
commit
eedd446b36
7 changed files with 302 additions and 55 deletions
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { useAuth } from "@/lib/auth-client"
|
||||
|
|
@ -17,12 +18,19 @@ function checkLiveChatApiExists() {
|
|||
// Importacao dinamica para evitar problemas de SSR
|
||||
const ChatWidget = dynamic(
|
||||
() => import("./chat-widget").then((mod) => ({ default: mod.ChatWidget })),
|
||||
{ ssr: false }
|
||||
{ ssr: false, loading: () => null }
|
||||
)
|
||||
|
||||
export function ChatWidgetProvider() {
|
||||
const { role, isLoading } = useAuth()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
// Evita hydration mismatch - so renderiza apos montar no cliente
|
||||
if (!mounted) return null
|
||||
if (isLoading) return null
|
||||
if (!isAgentOrAdmin(role)) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||
import { Separator } from "@/components/ui/separator"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface NotificationType {
|
||||
type: string
|
||||
|
|
@ -304,37 +305,57 @@ export function NotificationPreferencesForm({ isPortal = false }: NotificationPr
|
|||
<p className="text-sm text-muted-foreground">{group.description}</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{types.map((notifType) => (
|
||||
<div
|
||||
key={notifType.type}
|
||||
className="flex items-center justify-between rounded-lg border p-4"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{notifType.required ? (
|
||||
<Lock className="h-4 w-4 text-muted-foreground" />
|
||||
) : localTypePrefs[notifType.type] ? (
|
||||
<Bell className="h-4 w-4 text-primary" />
|
||||
) : (
|
||||
<BellOff className="h-4 w-4 text-muted-foreground" />
|
||||
{types.map((notifType) => {
|
||||
const isEnabled = localTypePrefs[notifType.type] ?? notifType.enabled
|
||||
return (
|
||||
<div
|
||||
key={notifType.type}
|
||||
className={cn(
|
||||
"flex items-center justify-between rounded-lg border p-4 transition-colors",
|
||||
isEnabled ? "border-foreground/20 bg-foreground/[0.02]" : "border-border"
|
||||
)}
|
||||
<div>
|
||||
<Label className="text-sm font-medium">
|
||||
{notifType.label}
|
||||
</Label>
|
||||
{notifType.required && (
|
||||
<Badge variant="secondary" className="ml-2 text-xs">
|
||||
Obrigatório
|
||||
</Badge>
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-8 w-8 items-center justify-center rounded-full transition-colors",
|
||||
notifType.required
|
||||
? "bg-muted text-muted-foreground"
|
||||
: isEnabled
|
||||
? "bg-foreground text-background"
|
||||
: "bg-muted text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{notifType.required ? (
|
||||
<Lock className="h-4 w-4" />
|
||||
) : isEnabled ? (
|
||||
<Bell className="h-4 w-4" />
|
||||
) : (
|
||||
<BellOff className="h-4 w-4" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Label className={cn(
|
||||
"text-sm font-medium transition-colors",
|
||||
isEnabled ? "text-foreground" : "text-muted-foreground"
|
||||
)}>
|
||||
{notifType.label}
|
||||
</Label>
|
||||
{notifType.required && (
|
||||
<Badge variant="secondary" className="ml-2 text-xs bg-foreground text-background">
|
||||
Obrigatório
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
checked={isEnabled}
|
||||
onCheckedChange={(checked) => toggleType(notifType.type, checked)}
|
||||
disabled={!notifType.canDisable}
|
||||
/>
|
||||
</div>
|
||||
<Switch
|
||||
checked={localTypePrefs[notifType.type] ?? notifType.enabled}
|
||||
onCheckedChange={(checked) => toggleType(notifType.type, checked)}
|
||||
disabled={!notifType.canDisable}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
// Evita hydration mismatch - so renderiza apos montar no cliente
|
||||
if (!mounted) return null
|
||||
|
||||
const baseClass =
|
||||
"inline-flex w-auto min-w-0 items-center justify-center gap-2 self-center rounded-xl border border-black bg-black px-3 py-2 text-sm font-medium text-white shadow-lg"
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ const Switch = React.forwardRef<
|
|||
<SwitchPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-input transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-sidebar-accent data-[state=checked]:text-sidebar-accent-foreground",
|
||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-transparent bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=checked]:border-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
className={cn(
|
||||
"pointer-events-none block h-5 w-5 translate-x-0 rounded-full bg-background shadow transition-transform duration-200 data-[state=checked]:translate-x-[20px]",
|
||||
"pointer-events-none block h-5 w-5 translate-x-0 rounded-full bg-background shadow-sm transition-transform duration-200 data-[state=checked]:translate-x-[20px] data-[state=checked]:bg-background",
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue