diff --git a/.gitignore b/.gitignore index a325ed92..c4157a28 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,12 @@ jspm_packages/ /dist /.cache +# TanStack Router generated route tree +app/routeTree.gen.ts + +# Cloudflare +.wrangler/ + # Environment variables .env .env.local @@ -48,9 +54,6 @@ jspm_packages/ .env.test.local .env.production.local -# Vercel -.vercel - # Workspace files *.code-workspace ameciclo.code-workspace diff --git a/app/client.tsx b/app/client.tsx new file mode 100644 index 00000000..ab261e34 --- /dev/null +++ b/app/client.tsx @@ -0,0 +1,30 @@ +import { StartClient } from "@tanstack/react-start/client"; +import { StrictMode } from "react"; +import { hydrateRoot } from "react-dom/client"; + +// Suppress react-map-gl warnings and errors +const originalWarn = console.warn; +const originalError = console.error; + +console.warn = (...args: any[]) => { + const message = String(args[0] || ""); + if (message.includes("Marker") && message.includes("defaultProps")) { + return; + } + originalWarn.apply(console, args); +}; + +console.error = (...args: any[]) => { + const message = String(args[0] || ""); + if (message.includes("setLayerProperty is not a function")) { + return; + } + originalError.apply(console, args); +}; + +hydrateRoot( + document, + + + +); diff --git a/app/components/Agenda/EventCalendar.tsx b/app/components/Agenda/EventCalendar.tsx index e46c2dab..3c26de5c 100644 --- a/app/components/Agenda/EventCalendar.tsx +++ b/app/components/Agenda/EventCalendar.tsx @@ -1,5 +1,4 @@ -import { useState, useEffect } from "react"; -import { ClientOnly } from "remix-utils/client-only"; +import { useState, useEffect, type ReactNode } from "react"; const CALENDAR_VIEW_COOKIE = "ameciclo_calendar_view"; @@ -118,6 +117,12 @@ function CalendarComponent({ googleCalendarApiKey, externalCalendarId, internalC ); } +function ClientOnly({ children, fallback }: { children: () => ReactNode; fallback: ReactNode }) { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + return <>{mounted ? children() : fallback}; +} + export default function EventCalendar(props: any) { return ( s.location }); const [showDetails, setShowDetails] = useState(false); const [dismissed, setDismissed] = useState(false); - const modalRef = useRef(null); - + useEffect(() => { // Limpar erros ao trocar de página setDismissed(false); }, [location.pathname]); - - useEffect(() => { - if (showDetails && modalRef.current) { - const focusableElements = modalRef.current.querySelectorAll( - 'button, a, [tabindex]:not([tabindex="-1"])' - ); - const firstElement = focusableElements[0] as HTMLElement; - const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement; - - const handleTab = (e: KeyboardEvent) => { - if (e.key === 'Tab') { - if (e.shiftKey) { - if (document.activeElement === firstElement) { - e.preventDefault(); - lastElement?.focus(); - } - } else { - if (document.activeElement === lastElement) { - e.preventDefault(); - firstElement?.focus(); - } - } - } - if (e.key === 'Escape') { - setShowDetails(false); - } - }; - document.addEventListener('keydown', handleTab); - firstElement?.focus(); - - return () => document.removeEventListener('keydown', handleTab); - } - }, [showDetails]); - - - const generateErrorReport = () => { const errorDetails = `RELATÓRIO DE ERRO AUTOMÁTICO\n\nPágina: ${location.pathname}\nHorário: ${new Date().toLocaleString('pt-BR')}\n\nAPIs com falha:\n${apiErrors.map(error => `- ${error.url}\n Erro: ${error.error}\n Página: ${error.page}\n Horário: ${error.timestamp}`).join('\n\n')}\n\nEmail (obrigatório):\n\nTelefone (opcional):\n\nInformações adicionais (opcional):`; return encodeURIComponent(errorDetails); @@ -64,14 +30,14 @@ export function ApiAlert() {

Esta página está sofrendo instabilidade agora.{' '} -

-
- - {showDetails && ( -
setShowDetails(false)} + + + setShowDetails(false)} + className="max-w-2xl max-h-[80vh] overflow-y-auto" > -
e.stopPropagation()} - role="dialog" - aria-modal="true" - aria-labelledby="error-dialog-title" - > - - -

Detalhes do Erro

-
+ + Detalhes do Erro + + +
+
+

Página atual: {location.pathname}

+

Horário: {new Date().toLocaleString('pt-BR')}

+
+ + {apiErrors.length > 0 ? (
-

Página atual: {location.pathname}

-

Horário: {new Date().toLocaleString('pt-BR')}

-
- - {apiErrors.length > 0 ? ( -
-

APIs com falha:

-
- {apiErrors.map((error, index) => ( -
+

APIs com falha:

+
+ {apiErrors.map((error, index) => ( + +

API: {error.url}

Erro: {error.error}

Página: {error.page}

Horário: {error.timestamp}

-
- ))} -
+ + + ))}
- ) : ( -

Nenhum erro específico registrado.

- )} -
-
- +
+ ) : ( +

Nenhum erro específico registrado.

+ )} +
+ + +
-
- )} +
+
); -} \ No newline at end of file +} diff --git a/app/components/Commom/Breadcrumb.tsx b/app/components/Commom/Breadcrumb.tsx index 9b0d0923..09b49b5b 100644 --- a/app/components/Commom/Breadcrumb.tsx +++ b/app/components/Commom/Breadcrumb.tsx @@ -1,4 +1,4 @@ -import { Link } from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; interface BreadcrumbItemProps { slug: string; diff --git a/app/components/Commom/CachePermissionModal.tsx b/app/components/Commom/CachePermissionModal.tsx index 1719ecf3..31091dd8 100644 --- a/app/components/Commom/CachePermissionModal.tsx +++ b/app/components/Commom/CachePermissionModal.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { useFocusTrap } from '~/hooks/useFocusTrap'; -import { X } from 'lucide-react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '~/components/ui/dialog'; +import { Button } from '~/components/ui/button'; interface CachePermissionBarProps { onAllow: () => void; @@ -9,7 +9,6 @@ interface CachePermissionBarProps { export default function CachePermissionBar({ onAllow, onDeny }: CachePermissionBarProps) { const [isVisible, setIsVisible] = useState(false); - const modalRef = useFocusTrap(isVisible); useEffect(() => { if (typeof window !== 'undefined') { @@ -23,10 +22,6 @@ export default function CachePermissionBar({ onAllow, onDeny }: CachePermissionB } }, []); - const handleClose = () => { - setIsVisible(false); - }; - const handleAllow = () => { localStorage.setItem('cache-permission', 'allowed'); setIsVisible(false); @@ -39,59 +34,47 @@ export default function CachePermissionBar({ onAllow, onDeny }: CachePermissionB onDeny(); }; - if (!isVisible) return null; - return ( -
- -
-
-
+ +
-

+ Melhorar sua experiência -

-

- Utilizamos cache local para acelerar o carregamento das páginas e melhorar sua navegação. + +

+ Utilizamos cache local para acelerar o carregamento das páginas e melhorar sua navegação. Os dados são armazenados temporariamente apenas no seu navegador.

-
- - -
-
-
-
+ + + + + + + ); -} \ No newline at end of file +} diff --git a/app/components/Commom/CardsSession.tsx b/app/components/Commom/CardsSession.tsx index b5361a31..61126292 100644 --- a/app/components/Commom/CardsSession.tsx +++ b/app/components/Commom/CardsSession.tsx @@ -1,4 +1,4 @@ -import { Link } from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; import { ExternalLink } from 'lucide-react'; import DevelopingComponent from "./DevelopingComponent"; diff --git a/app/components/Commom/ErrorBoundary.tsx b/app/components/Commom/ErrorBoundary.tsx index adf53346..68ab1526 100644 --- a/app/components/Commom/ErrorBoundary.tsx +++ b/app/components/Commom/ErrorBoundary.tsx @@ -1,10 +1,5 @@ -import { useRouteError, isRouteErrorResponse } from "@remix-run/react"; - -export function ErrorBoundary() { - const error = useRouteError(); - - if (isRouteErrorResponse(error)) { - if (error.status === 504) { +export function ErrorBoundary({ error }: { error: any }) { + if (error?.status === 504) { return (
@@ -21,7 +16,6 @@ export function ErrorBoundary() {
); - } } return ( diff --git a/app/components/Commom/ErrorFallback.tsx b/app/components/Commom/ErrorFallback.tsx index e2392ca7..3928fb7e 100644 --- a/app/components/Commom/ErrorFallback.tsx +++ b/app/components/Commom/ErrorFallback.tsx @@ -1,3 +1,7 @@ +import { Alert, AlertTitle, AlertDescription } from '~/components/ui/alert'; +import { Button } from '~/components/ui/button'; +import { AlertCircle } from 'lucide-react'; + function getCurrentRoute(): string { return typeof window !== "undefined" ? window.location.pathname : "DESCONHECIDA"; } @@ -14,17 +18,20 @@ interface ErrorFallbackProps { export default function ErrorFallback({ error }: ErrorFallbackProps) { return (
-

Ocorreu um erro

- <> -

Tente mais tarde

-

{error.status}

- {error.data?.message &&

Detalhes: {error.data.message}

} + + + Ocorreu um erro + +

Tente mais tarde

+ {error.status &&

{error.status}

} + {error.data?.message &&

Detalhes: {error.data.message}

} - +
+
); } @@ -63,13 +70,13 @@ Erro: ${errorMessage} const whatsappLink = `https://wa.me/558197860060?text=${encodedMessage}`; return ( - <> -

Entre em contato:

+
+

Entre em contato:

- + - +
); } diff --git a/app/components/Commom/FeaturedProject.tsx b/app/components/Commom/FeaturedProject.tsx index d1272448..70890ff6 100644 --- a/app/components/Commom/FeaturedProject.tsx +++ b/app/components/Commom/FeaturedProject.tsx @@ -1,4 +1,4 @@ -import { Link } from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; export const FeaturedProject = ({ project }: { project: Project }) => { return ( diff --git a/app/components/Commom/Footer.tsx b/app/components/Commom/Footer.tsx index 17e97db7..f738cded 100644 --- a/app/components/Commom/Footer.tsx +++ b/app/components/Commom/Footer.tsx @@ -1,4 +1,4 @@ -import { Link } from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; import { footerColumn, footerColumnContent } from "../../../typings"; export const Footer = () => { @@ -100,9 +100,6 @@ export const Footer = () => {
))}
-
- -
); @@ -133,20 +130,3 @@ function FooterColumn({ column }: FooterColumnProps) { ); } -function VercelSponsor() { - return ( - - Vercel Logo - - ); -} diff --git a/app/components/Commom/MainContent.tsx b/app/components/Commom/MainContent.tsx index 69bf09ee..0c99e7a1 100644 --- a/app/components/Commom/MainContent.tsx +++ b/app/components/Commom/MainContent.tsx @@ -1,11 +1,11 @@ -import { useLocation } from "@remix-run/react"; +import { useRouterState } from "@tanstack/react-router"; interface MainContentProps { children: React.ReactNode; } export function MainContent({ children }: MainContentProps) { - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); const isCicloDadosPage = location.pathname === '/dados/ciclodados'; return ( diff --git a/app/components/Commom/Maps/AmecicloMap.tsx b/app/components/Commom/Maps/AmecicloMap.tsx index a7cb2ab7..6f500221 100644 --- a/app/components/Commom/Maps/AmecicloMap.tsx +++ b/app/components/Commom/Maps/AmecicloMap.tsx @@ -6,7 +6,7 @@ import { WebMercatorViewport } from "@math.gl/web-mercator"; import bbox from "@turf/bbox"; import * as turf from "@turf/helpers"; import { pointData } from "../../../../typings"; -import * as Remix from "@remix-run/react"; + import { Move } from 'lucide-react'; import { MapboxKeyWarning } from './MapboxKeyWarning'; diff --git a/app/components/Commom/Maps/MapboxKeyWarning.tsx b/app/components/Commom/Maps/MapboxKeyWarning.tsx index e47845a1..3841e596 100644 --- a/app/components/Commom/Maps/MapboxKeyWarning.tsx +++ b/app/components/Commom/Maps/MapboxKeyWarning.tsx @@ -1,8 +1,8 @@ import { AlertTriangle, Info, Mail } from 'lucide-react'; -import { Link, useLocation } from '@remix-run/react'; +import { Link, useRouterState } from '@tanstack/react-router'; export const MapboxKeyWarning = () => { - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); const currentPage = location.pathname; const errorMessage = encodeURIComponent(`Erro no mapa da página: ${currentPage}\n\nDescrição: A chave de acesso do serviço de mapas não está configurada.`); const subject = encodeURIComponent('Erro Técnico - Mapa não carrega'); diff --git a/app/components/Commom/NavBar/DataSubmenu.tsx b/app/components/Commom/NavBar/DataSubmenu.tsx index c880d915..0402d0b1 100644 --- a/app/components/Commom/NavBar/DataSubmenu.tsx +++ b/app/components/Commom/NavBar/DataSubmenu.tsx @@ -1,4 +1,4 @@ -import { Link, useLocation } from "@remix-run/react"; +import { Link, useRouterState } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { ComingSoonButton } from "./ComingSoonButton"; @@ -17,7 +17,7 @@ const dataSubPages = [ ]; export function DataSubmenu() { - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); return ( { const [isMenuOpen, setIsMenuOpen] = useState(false); const [hideRedNavbar, setHideRedNavbar] = useState(false); const [isSubmenuVisible, setIsSubmenuVisible] = useState(false); - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); const isDataPage = location.pathname.startsWith('/dados') && location.pathname !== '/dados/ciclodados'; const isCicloDadosPage = location.pathname === '/dados/ciclodados'; @@ -133,7 +133,7 @@ export const Navbar = ({ pages }: any) => { }; function BigMenu({ pages, setIsSubmenuVisible, isSubmenuVisible }: any) { - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); const isActivePage = (pageUrl: string) => { if (pageUrl === "/") { @@ -187,7 +187,7 @@ function BigMenu({ pages, setIsSubmenuVisible, isSubmenuVisible }: any) { } function SmallMenu({ pages, closeMenu }: any) { - const location = useLocation(); + const location = useRouterState({ select: (s) => s.location }); const [isDataSubmenuOpen, setIsDataSubmenuOpen] = useState(false); const isActivePage = (pageUrl: string) => { diff --git a/app/components/Commom/PageNotFound.tsx b/app/components/Commom/PageNotFound.tsx index c4d7d539..9da8874f 100644 --- a/app/components/Commom/PageNotFound.tsx +++ b/app/components/Commom/PageNotFound.tsx @@ -1,4 +1,4 @@ -import { Link } from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; import image from "/crash.webp"; export default function PageNotFound() { diff --git a/app/components/Commom/SelectionFilter.tsx b/app/components/Commom/SelectionFilter.tsx index de2e052d..5040c073 100644 --- a/app/components/Commom/SelectionFilter.tsx +++ b/app/components/Commom/SelectionFilter.tsx @@ -1,33 +1,29 @@ import { Filter } from 'lucide-react'; +import { Select } from '~/components/ui/select'; +import { Label } from '~/components/ui/label'; +import { cn } from '~/lib/utils'; export function SelectionFilter({ title, value, name, onChange, items }: any) { const filterId = `filter-${name || title?.toLowerCase().replace(/\s+/g, '-')}`; - + return (
- +
); } diff --git a/app/components/Commom/Table/Table.tsx b/app/components/Commom/Table/Table.tsx index 174ba684..ca3c2b72 100644 --- a/app/components/Commom/Table/Table.tsx +++ b/app/components/Commom/Table/Table.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { matchSorter } from "match-sorter"; -import * as Remix from "@remix-run/react"; +import { Link } from "@tanstack/react-router"; import { useTable, usePagination, useFilters, useSortBy, useExpanded } from "react-table"; import { ChevronsUpDown, ChevronUp, ChevronDown } from "lucide-react"; @@ -495,12 +495,12 @@ const Table = ({ title, data, columns, allColumns, showFilters, setShowFilters,
- Reportar erro - +
-
+ + {point.popup?.name} +
- +
@@ -36,7 +34,7 @@ export function PointDetailsModal({ point, onClose }: PointDetailsModalProps) { }`}>
{point.popup?.total} ciclistas
- +
Data
@@ -47,7 +45,7 @@ export function PointDetailsModal({ point, onClose }: PointDetailsModalProps) {
{point.type === 'prefeitura' ? 'PCR' : 'Ameciclo'}
- + {point.popup?.obs && (
Observações
@@ -56,7 +54,7 @@ export function PointDetailsModal({ point, onClose }: PointDetailsModalProps) { )}
- - + + ); } diff --git a/app/components/Contato/ContactForm.tsx b/app/components/Contato/ContactForm.tsx index d374177a..fd95aeab 100644 --- a/app/components/Contato/ContactForm.tsx +++ b/app/components/Contato/ContactForm.tsx @@ -1,6 +1,12 @@ import { useState } from "react"; -import { useSearchParams } from "@remix-run/react"; +import { useSearch } from "@tanstack/react-router"; import { Mail, MessageCircle, Check, X } from "lucide-react"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { Textarea } from "~/components/ui/textarea"; +import { Select } from "~/components/ui/select"; +import { Button } from "~/components/ui/button"; +import { cn } from "~/lib/utils"; interface FormData { nome: string; @@ -12,8 +18,8 @@ interface FormData { } export function ContactForm() { - const [searchParams] = useSearchParams(); - const initialMessage = searchParams.get("message") || ""; + const searchParams = useSearch({ strict: false }); + const initialMessage = (searchParams as any).message || ""; const [errors, setErrors] = useState<{[key: string]: string}>({}); const [success, setSuccess] = useState<{[key: string]: boolean}>({}); @@ -28,7 +34,7 @@ export function ContactForm() { const validateForm = (data: FormData): {[key: string]: string} => { const newErrors: {[key: string]: string} = {}; - + if (!data.nome) newErrors.nome = 'Nome é obrigatório'; if (!data.email) { newErrors.email = 'Email é obrigatório'; @@ -40,20 +46,20 @@ export function ContactForm() { } if (!data.mensagem) newErrors.mensagem = 'Mensagem é obrigatória'; if (!data.lgpdChecked) newErrors.lgpd = 'Você precisa aceitar os termos da LGPD'; - + return newErrors; }; const handleFormSubmit = (callback: (data: FormData) => void) => { const data = getFormData(); const validationErrors = validateForm(data); - + if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); document.getElementById(Object.keys(validationErrors)[0])?.focus(); return; } - + setErrors({}); callback(data); }; @@ -61,16 +67,19 @@ export function ContactForm() { return (

Entre em Contato

- +
- +
- { const value = e.target.value; @@ -83,15 +92,18 @@ export function ContactForm() {
{errors.nome &&

{errors.nome}

}
- +
- +
- { const value = e.target.value; @@ -109,22 +121,25 @@ export function ContactForm() {
{errors.email &&

{errors.email}

}
- +
- +
- + +
- { const input = e.target as HTMLInputElement; @@ -147,15 +162,18 @@ export function ContactForm() {
{errors.telefone &&

{errors.telefone}

}
- +
- +
-