diff --git a/src/App.tsx b/src/App.tsx index 03936be..53ce76a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,10 @@ import { Navigate, Route, Routes } from 'react-router-dom'; -import { LoginSlide } from './views/components/login-slide'; import { RetrospectiveSlides } from './views/components/retrospective-slides'; export function App() { return ( - } /> - } /> + } /> } /> ); diff --git a/src/app/config/local-storage-keys.ts b/src/app/config/local-storage-keys.ts new file mode 100644 index 0000000..eb089ee --- /dev/null +++ b/src/app/config/local-storage-keys.ts @@ -0,0 +1,3 @@ +export const localStorageKeys = { + ACCESS_TOKEN: '@review:token', +}; diff --git a/src/app/hooks/use-auth.tsx b/src/app/hooks/use-auth.tsx new file mode 100644 index 0000000..e02de53 --- /dev/null +++ b/src/app/hooks/use-auth.tsx @@ -0,0 +1,78 @@ +/* eslint-disable react-refresh/only-export-components */ +import { api } from '@/app/services'; +import { createContext, useCallback, useContext, useState } from 'react'; +import { localStorageKeys } from '../config/local-storage-keys'; + +type AuthState = { + token: string; +}; + +type AuthContextValue = { + signedIn: boolean; + authenticate(token: string): void; + signOut(): void; +}; + +export const AuthContext = createContext({} as AuthContextValue); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [isSignedIn, setIsSignedIn] = useState(false); + + const [, setAuthState] = useState(() => { + const token = localStorage.getItem(localStorageKeys.ACCESS_TOKEN); + + if (token) { + api.defaults.headers.Authorization = `Bearer ${token}`; + + setIsSignedIn(true); + return { token }; + } + + return {} as AuthState; + }); + + const authenticate = useCallback((token: string) => { + try { + localStorage.setItem(localStorageKeys.ACCESS_TOKEN, token); + + api.defaults.headers.Authorization = `Bearer ${token}`; + + setIsSignedIn(true); + setAuthState({ token }); + } catch (error) { + console.error(error); + } + }, []); + + const signOut = useCallback(() => { + localStorage.removeItem(localStorageKeys.ACCESS_TOKEN); + + setIsSignedIn(false); + + setAuthState({} as AuthState); + + api.defaults.headers.Authorization = ''; + api.defaults.headers['Cache-Control'] = 'no-cache'; + }, []); + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + + return context; +} diff --git a/src/app/hooks/useMetrics.ts b/src/app/hooks/useMetrics.ts index fa8c72a..09f3a84 100644 --- a/src/app/hooks/useMetrics.ts +++ b/src/app/hooks/useMetrics.ts @@ -1,18 +1,18 @@ import { useQuery } from '@tanstack/react-query'; import { metricsService } from '../services/metrics'; -export function useMetricsByPortal(cpf: string, ano: number) { +export function useMetricsByPortal(ano: number) { return useQuery({ - queryKey: ['metricsByPortal', cpf, ano], - queryFn: () => metricsService.getMetricsByPortal({ cpf, ano }), - enabled: !!cpf && !!ano, + queryKey: ['metricsByPortal', ano], + queryFn: () => metricsService.getMetricsByPortal({ ano }), + enabled: !!ano, }); } -export function useMetricsBySystems(cpf: string, ano: number) { +export function useMetricsBySystems(ano: number) { return useQuery({ - queryKey: ['metricsBySystems', cpf, ano], - queryFn: () => metricsService.getMetricsBySystems({ cpf, ano }), - enabled: !!cpf && !!ano, + queryKey: ['metricsBySystems', ano], + queryFn: () => metricsService.getMetricsBySystems({ ano }), + enabled: !!ano, }); } diff --git a/src/app/services/index.ts b/src/app/services/index.ts index 806180a..96a589f 100644 --- a/src/app/services/index.ts +++ b/src/app/services/index.ts @@ -6,10 +6,12 @@ const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); -// Adicionar token temporariamente aos headers -const token = import.meta.env.VITE_API_TOKEN || ''; -if (token) { - api.defaults.headers.common['Authorization'] = `Bearer ${token}`; +export function setAuthToken(newToken: string | null) { + if (newToken) { + api.defaults.headers.common['Authorization'] = `Bearer ${newToken}`; + } else { + delete api.defaults.headers.common['Authorization']; + } } export const queryClient = new QueryClient({ diff --git a/src/app/services/metrics/metrics-by-portal.ts b/src/app/services/metrics/metrics-by-portal.ts index 709d0c4..dcd0441 100644 --- a/src/app/services/metrics/metrics-by-portal.ts +++ b/src/app/services/metrics/metrics-by-portal.ts @@ -8,15 +8,13 @@ type MetricsByPortalProps = { type GetMetricsByPortalParams = { ano: number; - cpf: string; }; export async function getMetricsByPortal({ ano, - cpf, }: GetMetricsByPortalParams): Promise { const { data } = await api.get( - `/userAccessMetricsByPortal?ano=${ano}&cpf=${cpf}`, + `/userAccessMetricsByPortal?ano=${ano}`, ); return data; diff --git a/src/app/services/metrics/metrics-by-systems.ts b/src/app/services/metrics/metrics-by-systems.ts index 13b3e3f..7a1b113 100644 --- a/src/app/services/metrics/metrics-by-systems.ts +++ b/src/app/services/metrics/metrics-by-systems.ts @@ -8,16 +8,12 @@ type MetricsBySystemsProps = { type GetMetricsBySystemsParams = { ano: number; - cpf: string; }; export async function getMetricsBySystems({ ano, - cpf, }: GetMetricsBySystemsParams): Promise { - const { data } = await api.get( - `/userAccessMetricsBySystem?ano=${ano}&cpf=${cpf}`, - ); + const { data } = await api.get(`/userAccessMetricsBySystem?ano=${ano}`); return data; } diff --git a/src/main.tsx b/src/main.tsx index af009d5..9f95cc3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -14,13 +14,16 @@ import { ptBR } from 'date-fns/locale'; setDefaultOptions({ locale: ptBR }); import '@/styles/index.css'; +import { AuthProvider } from './app/hooks/use-auth'; createRoot(document.getElementById('root')!).render( - - - + + + + + , ); diff --git a/src/views/components/retrospective-slides.tsx b/src/views/components/retrospective-slides.tsx index 5fddf07..54abbe9 100644 --- a/src/views/components/retrospective-slides.tsx +++ b/src/views/components/retrospective-slides.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { useAuth } from '@/app/hooks/use-auth'; import { useMetricsByPortal, useMetricsBySystems, @@ -6,28 +8,50 @@ import logoAImg from '@/assets/images/a-agape.png'; import logoImg from '@/assets/images/agape-logo.png'; import { ChartLineUpIcon, SpinnerIcon } from '@phosphor-icons/react'; import { motion } from 'framer-motion'; -import { useSearchParams } from 'react-router-dom'; +import { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; import ProfileCard from './ProfileCard'; import { StorySlide } from './story-slide'; export function RetrospectiveSlides() { - const [searchParams] = useSearchParams(); - const cpf = searchParams.get('cpf') || ''; - const ano = searchParams.get('ano') - ? parseInt(searchParams.get('ano')!) - : new Date().getFullYear(); + const { token } = useParams(); + const { authenticate } = useAuth(); + + useEffect(() => { + if (token) { + authenticate(token); + } + }, [token, authenticate]); + + const year = new Date().getFullYear(); + + if (!token) { + return ( +
+
+

+ Token inválido ou ausente +

+

+ Esta aplicação deve ser acessada com o parâmetro token{' '} + na URL. Exemplo: ?token=SEU_TOKEN&cpf=00000000000 +

+
+
+ ); + } const { data: metricsPortal, isLoading: isLoadingPortal, error: errorPortal, - } = useMetricsByPortal(cpf, ano); + } = useMetricsByPortal(year); const { data: metricsSystems, isLoading: isLoadingSystems, error: errorSystems, - } = useMetricsBySystems(cpf, ano); + } = useMetricsBySystems(year); const slides: number[] = []; slides.push(0); @@ -108,7 +132,7 @@ export function RetrospectiveSlides() { transition={{ duration: 0.6, delay: 0.2 }}> Sua Retrospectiva
- Ágape {ano} + Ágape {year} 0 && ( <> - -
-
-
-
- +
- Acessos em {ano} + Acessos em {year}
@@ -301,7 +320,8 @@ export function RetrospectiveSlides() { }}>

{( - (item.totalAcessos / (totalAcessos || 1)) * + (item.totalAcessos / + (totalAcessos || 1)) * 100 ).toFixed(0)} % @@ -349,7 +369,7 @@ export function RetrospectiveSlides() {

- A Ágape deseja a você um {ano + 1} repleto de sucesso! + {'<'} O futuro é programado aqui! Feliz {year + 1}! {'/>'}

@@ -365,7 +385,7 @@ export function RetrospectiveSlides() { whileInView={{ opacity: 1 }} viewport={{ once: true }} transition={{ duration: 0.6, delay: 0.4 }}> -

© {ano} Ágape Sistemas e Tecnologia

+

© {year} Ágape Sistemas e Tecnologia

O Futuro da Gestão Pública começa aqui!

@@ -400,4 +420,4 @@ export function RetrospectiveSlides() { )} ); -} \ No newline at end of file +}