"use client"; import { createFileRoute } from "@tanstack/react-router"; import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { IMaskInput } from "react-imask"; import { toast } from "sonner"; import { Loader2, LogOut, Globe } from "lucide-react"; import { supabase } from "@/integrations/supabase/client"; import { useAuth } from "@/hooks/use-auth"; import { useProfile } from "@/hooks/use-profile"; import { translateAuthError } from "@/lib/auth-errors"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { TIMEZONE_OPTIONS } from "@/lib/timezones"; export const Route = createFileRoute("/painel/configuracoes")({ component: SettingsPage, }); const profileSchema = z.object({ full_name: z.string().min(2, "Informe seu nome completo.").max(120), company_name: z.string().max(120).optional().or(z.literal("")), cpf_cnpj: z.string().optional().or(z.literal("")), phone: z.string().optional().or(z.literal("")), }); type ProfileForm = z.infer; function SettingsPage() { const { user } = useAuth(); const { data: profile, isLoading } = useProfile(); const queryClient = useQueryClient(); const { register, handleSubmit, setValue, watch, reset, formState: { errors } } = useForm({ resolver: zodResolver(profileSchema), }); useEffect(() => { if (profile) { reset({ full_name: profile.full_name, company_name: profile.company_name ?? "", cpf_cnpj: profile.cpf_cnpj ?? "", phone: profile.phone ?? "", }); } }, [profile, reset]); const cpfCnpj = watch("cpf_cnpj") || ""; const phone = watch("phone") || ""; const cpfCnpjDigits = cpfCnpj.replace(/\D/g, ""); const cpfCnpjMask = cpfCnpjDigits.length > 11 ? "00.000.000/0000-00" : "000.000.000-00"; const updateProfile = useMutation({ mutationFn: async (data: ProfileForm) => { if (!user) throw new Error("Sem sessão"); const { error } = await supabase .from("profiles") .update({ full_name: data.full_name, company_name: data.company_name || null, cpf_cnpj: data.cpf_cnpj || null, phone: data.phone || null, }) .eq("id", user.id); if (error) throw error; }, onSuccess: () => { toast.success("Perfil atualizado com sucesso."); queryClient.invalidateQueries({ queryKey: ["profile"] }); }, onError: (e: Error) => toast.error(translateAuthError(e.message)), }); const signOutAll = async () => { const { error } = await supabase.auth.signOut({ scope: "global" }); if (error) toast.error(translateAuthError(error.message)); else toast.success("Saiu de todos os dispositivos."); }; if (isLoading) return ; return (

Configurações

Gerencie seu perfil e segurança.

Perfil

Informações para faturamento e suporte.

updateProfile.mutate(d))} className="space-y-4">
{errors.full_name &&

{errors.full_name.message}

}
setValue("cpf_cnpj", v as string, { shouldValidate: true })} placeholder="000.000.000-00" className="flex h-10 w-full rounded-lg border border-input bg-transparent px-3 py-2 text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20" />
setValue("phone", v as string, { shouldValidate: true })} placeholder="(11) 99999-9999" className="flex h-10 w-full rounded-lg border border-input bg-transparent px-3 py-2 text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20" />

Segurança

Para alterar sua senha, use o link de recuperação no e-mail.

); } function TimezoneSection({ currentTimezone, userId, }: { currentTimezone: string; userId: string | undefined; }) { const queryClient = useQueryClient(); const [value, setValue] = useState(currentTimezone); useEffect(() => { setValue(currentTimezone); }, [currentTimezone]); const grouped = useMemo(() => { const groups: Record = {}; for (const tz of TIMEZONE_OPTIONS) { (groups[tz.group] ??= []).push(tz); } return groups; }, []); const detected = useMemo(() => { try { return Intl.DateTimeFormat().resolvedOptions().timeZone; } catch { return null; } }, []); const nowInTz = useMemo(() => { try { return new Intl.DateTimeFormat("pt-BR", { timeZone: value, dateStyle: "short", timeStyle: "medium", }).format(new Date()); } catch { return "—"; } }, [value]); const update = useMutation({ mutationFn: async (tz: string) => { if (!userId) throw new Error("Sem sessão"); const { error } = await supabase .from("profiles") .update({ timezone: tz }) .eq("id", userId); if (error) throw error; }, onSuccess: () => { toast.success("Fuso horário atualizado."); queryClient.invalidateQueries({ queryKey: ["profile"] }); }, onError: (e: Error) => toast.error(translateAuthError(e.message)), }); const dirty = value !== currentTimezone; const isDetectedListed = detected ? TIMEZONE_OPTIONS.some((t) => t.value === detected) : false; return (

Fuso horário

Usado para agendar e exibir suas automações (cronjobs).

Agora neste fuso: {nowInTz}

{detected && detected !== value && (

Seu navegador está em {detected}. {isDetectedListed && ( )}

)}
); }