From f91f1098e7cd8e661e43cdae7baf054759698985 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:09:35 +0000 Subject: [PATCH 1/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/lib/timezones.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/lib/timezones.ts diff --git a/src/lib/timezones.ts b/src/lib/timezones.ts new file mode 100644 index 0000000..db255ca --- /dev/null +++ b/src/lib/timezones.ts @@ -0,0 +1,55 @@ +export interface TimezoneOption { + value: string; + label: string; + group: string; +} + +export const TIMEZONE_OPTIONS: TimezoneOption[] = [ + // Brasil + { value: "America/Sao_Paulo", label: "Brasília (GMT-3) — São Paulo, Rio, BH", group: "Brasil" }, + { value: "America/Bahia", label: "Salvador (GMT-3)", group: "Brasil" }, + { value: "America/Fortaleza", label: "Fortaleza (GMT-3)", group: "Brasil" }, + { value: "America/Recife", label: "Recife (GMT-3)", group: "Brasil" }, + { value: "America/Belem", label: "Belém (GMT-3)", group: "Brasil" }, + { value: "America/Manaus", label: "Manaus (GMT-4) — Amazonas", group: "Brasil" }, + { value: "America/Cuiaba", label: "Cuiabá (GMT-4) — Mato Grosso", group: "Brasil" }, + { value: "America/Porto_Velho", label: "Porto Velho (GMT-4) — Rondônia", group: "Brasil" }, + { value: "America/Boa_Vista", label: "Boa Vista (GMT-4) — Roraima", group: "Brasil" }, + { value: "America/Rio_Branco", label: "Rio Branco (GMT-5) — Acre", group: "Brasil" }, + { value: "America/Noronha", label: "Fernando de Noronha (GMT-2)", group: "Brasil" }, + + // Américas + { value: "America/New_York", label: "Nova York (GMT-5/-4)", group: "Américas" }, + { value: "America/Chicago", label: "Chicago (GMT-6/-5)", group: "Américas" }, + { value: "America/Denver", label: "Denver (GMT-7/-6)", group: "Américas" }, + { value: "America/Los_Angeles", label: "Los Angeles (GMT-8/-7)", group: "Américas" }, + { value: "America/Toronto", label: "Toronto (GMT-5/-4)", group: "Américas" }, + { value: "America/Mexico_City", label: "Cidade do México (GMT-6)", group: "Américas" }, + { value: "America/Buenos_Aires", label: "Buenos Aires (GMT-3)", group: "Américas" }, + { value: "America/Santiago", label: "Santiago (GMT-4/-3)", group: "Américas" }, + { value: "America/Bogota", label: "Bogotá (GMT-5)", group: "Américas" }, + { value: "America/Lima", label: "Lima (GMT-5)", group: "Américas" }, + + // Europa + { value: "Europe/London", label: "Londres (GMT+0/+1)", group: "Europa" }, + { value: "Europe/Lisbon", label: "Lisboa (GMT+0/+1)", group: "Europa" }, + { value: "Europe/Madrid", label: "Madri (GMT+1/+2)", group: "Europa" }, + { value: "Europe/Paris", label: "Paris (GMT+1/+2)", group: "Europa" }, + { value: "Europe/Berlin", label: "Berlim (GMT+1/+2)", group: "Europa" }, + { value: "Europe/Rome", label: "Roma (GMT+1/+2)", group: "Europa" }, + { value: "Europe/Amsterdam", label: "Amsterdã (GMT+1/+2)", group: "Europa" }, + { value: "Europe/Moscow", label: "Moscou (GMT+3)", group: "Europa" }, + + // Ásia / Oceania / África + { value: "Asia/Dubai", label: "Dubai (GMT+4)", group: "Outros" }, + { value: "Asia/Kolkata", label: "Mumbai / Nova Délhi (GMT+5:30)", group: "Outros" }, + { value: "Asia/Shanghai", label: "Xangai (GMT+8)", group: "Outros" }, + { value: "Asia/Tokyo", label: "Tóquio (GMT+9)", group: "Outros" }, + { value: "Australia/Sydney", label: "Sydney (GMT+10/+11)", group: "Outros" }, + { value: "Africa/Johannesburg", label: "Joanesburgo (GMT+2)", group: "Outros" }, + { value: "UTC", label: "UTC (GMT+0)", group: "Outros" }, +]; + +export function getTimezoneLabel(value: string): string { + return TIMEZONE_OPTIONS.find((t) => t.value === value)?.label ?? value; +} From 136f1b98cd311911df02094c579cebd09ea67021 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:09:49 +0000 Subject: [PATCH 2/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/hooks/use-profile.ts | 1 + src/routes/painel.configuracoes.tsx | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hooks/use-profile.ts b/src/hooks/use-profile.ts index 14055eb..1f94283 100644 --- a/src/hooks/use-profile.ts +++ b/src/hooks/use-profile.ts @@ -15,6 +15,7 @@ export interface Profile { stripe_customer_id: string | null; paddle_customer_id: string | null; onboarding_completed: boolean; + timezone: string; } export function useProfile() { diff --git a/src/routes/painel.configuracoes.tsx b/src/routes/painel.configuracoes.tsx index 9be147e..ef99503 100644 --- a/src/routes/painel.configuracoes.tsx +++ b/src/routes/painel.configuracoes.tsx @@ -1,14 +1,14 @@ "use client"; import { createFileRoute } from "@tanstack/react-router"; -import { useState, useEffect } from "react"; +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 } from "lucide-react"; +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"; @@ -17,6 +17,16 @@ 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, From badb1978f27f1960968f4032942c8545feaf7493 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:10:12 +0000 Subject: [PATCH 3/3] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/routes/painel.configuracoes.tsx | 130 ++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/routes/painel.configuracoes.tsx b/src/routes/painel.configuracoes.tsx index ef99503..1107c73 100644 --- a/src/routes/painel.configuracoes.tsx +++ b/src/routes/painel.configuracoes.tsx @@ -154,6 +154,8 @@ function SettingsPage() { + +

Segurança

@@ -167,3 +169,131 @@ function SettingsPage() { ); } + +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 && ( + + )} +

+ )} +
+ + +
+
+ ); +}