corregido refreshtoken y mejorado ver informacion ui por roles

This commit is contained in:
2026-02-10 21:45:34 -04:00
parent 63c39e399e
commit 42e802f8a7
22 changed files with 2438 additions and 324 deletions

View File

@@ -1,5 +1,6 @@
'use server';
import { safeFetchApi } from '@/lib';
import { cookies } from 'next/headers';
import { loginResponseSchema, UserFormValue } from '../schemas/login';
type LoginActionSuccess = {
@@ -17,18 +18,18 @@ type LoginActionSuccess = {
refresh_token: string;
refresh_expire_in: number;
};
}
};
type LoginActionError = {
type: 'API_ERROR' | 'VALIDATION_ERROR' | 'UNKNOWN_ERROR'; // **Asegúrate de que el tipo de `type` sea este aquí**
message: string;
details?: any;
type: 'API_ERROR' | 'VALIDATION_ERROR' | 'UNKNOWN_ERROR'; // **Asegúrate de que el tipo de `type` sea este aquí**
message: string;
details?: any;
};
// Si SignInAction también puede devolver null, asegúralo en su tipo de retorno
type LoginActionResult = LoginActionSuccess | LoginActionError | null;
export const SignInAction = async (payload: UserFormValue): Promise<LoginActionResult> => {
export const SignInAction = async (payload: UserFormValue) => {
const [error, data] = await safeFetchApi(
loginResponseSchema,
'/auth/sign-in',
@@ -36,12 +37,22 @@ export const SignInAction = async (payload: UserFormValue): Promise<LoginActionR
payload,
);
if (error) {
return {
type: error.type as 'API_ERROR' | 'VALIDATION_ERROR' | 'UNKNOWN_ERROR',
message: error.message,
details: error.details
};
return error;
} else {
// 2. GUARDAR REFRESH TOKEN EN COOKIE (La clave del cambio)
(await cookies()).set(
'refresh_token',
String(data?.tokens?.refresh_token),
{
httpOnly: true, // JavaScript no puede leerla
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 7 * 24 * 60 * 60, // Ej: 7 días (debe coincidir con tu backend)
},
);
return data;
}
};

View File

@@ -0,0 +1,27 @@
'use server';
import { safeFetchApi } from '@/lib';
import { cookies } from 'next/headers';
import { logoutResponseSchema } from '../schemas/logout';
export const logoutAction = async (user_id: string) => {
const payload = { user_id };
const [error, data] = await safeFetchApi(
logoutResponseSchema,
'/auth/sign-out',
'POST',
payload,
);
if (error) {
console.error('Error:', error);
// Devuelve un objeto con la propiedad 'type' para que el callback de NextAuth lo reconozca como un error
return {
type: 'API_ERROR',
message: error.message,
};
}
(await cookies()).delete('refresh_token');
};

View File

@@ -0,0 +1,5 @@
import z from 'zod';
export const logoutResponseSchema = z.object({
message: z.string(),
});

View File

@@ -4,7 +4,6 @@ import { tokensSchema } from './login';
// Esquema para el refresh token
export const refreshTokenSchema = z.object({
user_id: z.number(),
token: z.string(),
});

View File

@@ -1,12 +1,14 @@
'use client';
import { useRouter } from 'next/navigation';
import { Button } from '@repo/shadcn/button';
import { Heading } from '@repo/shadcn/heading';
import { Plus } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
export function SurveysHeader() {
const router = useRouter();
const { data: session } = useSession();
const role = session?.user.role[0]?.rol;
return (
<>
<div className="flex items-start justify-between">
@@ -14,11 +16,18 @@ export function SurveysHeader() {
title="Administración de Encuestas"
description="Gestiona las encuestas disponibles en la plataforma"
/>
<Button onClick={() => router.push(`/dashboard/administracion/encuestas/crear`)} size="sm">
<Plus className="h-4 w-4"/><span className='hidden sm:inline'>Agregar Encuesta</span>
</Button>
{['superadmin', 'admin'].includes(role ?? '') && (
<Button
onClick={() =>
router.push(`/dashboard/administracion/encuestas/crear`)
}
size="sm"
>
<Plus className="h-4 w-4" />
<span className="hidden sm:inline">Agregar Encuesta</span>
</Button>
)}
</div>
</>
);
}
}

View File

@@ -1,7 +1,7 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { AlertModal } from '@/components/modal/alert-modal';
import { useDeleteSurvey } from '@/feactures/surveys/hooks/use-mutation-surveys';
import { SurveyTable } from '@/feactures/surveys/schemas/survey';
import { Button } from '@repo/shadcn/button';
import {
Tooltip,
@@ -10,9 +10,9 @@ import {
TooltipTrigger,
} from '@repo/shadcn/tooltip';
import { Edit, Trash } from 'lucide-react';
import { SurveyTable } from '@/feactures/surveys/schemas/survey';
import { useDeleteSurvey } from '@/feactures/surveys/hooks/use-mutation-surveys';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
interface CellActionProps {
data: SurveyTable;
@@ -23,6 +23,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data }) => {
const [open, setOpen] = useState(false);
const { mutate: deleteSurvey } = useDeleteSurvey();
const router = useRouter();
const { data: session } = useSession();
const onConfirm = async () => {
try {
@@ -36,6 +37,8 @@ export const CellAction: React.FC<CellActionProps> = ({ data }) => {
}
};
const role = session?.user.role[0]?.rol;
return (
<>
<AlertModal
@@ -47,41 +50,48 @@ export const CellAction: React.FC<CellActionProps> = ({ data }) => {
description="Esta acción no se puede deshacer."
/>
<div className="flex gap-1">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => router.push(`/dashboard/administracion/encuestas/editar/${data.id!}`)}
>
<Edit className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
{['superadmin', 'admin'].includes(role ?? '') && (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() =>
router.push(
`/dashboard/administracion/encuestas/editar/${data.id!}`,
)
}
>
<Edit className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setOpen(true)}
>
<Trash className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Eliminar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setOpen(true)}
>
<Trash className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Eliminar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
</div>
</>
);

View File

@@ -9,7 +9,8 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@repo/shadcn/tooltip';
import { Edit, Eye, Trash, FileDown } from 'lucide-react';
import { Edit, Eye, Trash } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { TrainingViewModal } from '../training-view-modal';
@@ -25,6 +26,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
const [viewOpen, setViewOpen] = useState(false);
const { mutate: deleteTraining } = useDeleteTraining();
const router = useRouter();
const { data: session } = useSession();
const onConfirm = async () => {
try {
@@ -42,6 +44,8 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
window.open(`${apiUrl}/training/export/${id}`, '_blank');
};
const role = session?.user.role[0]?.rol;
return (
<>
<AlertModal
@@ -60,75 +64,70 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
/>
<div className="flex gap-1">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setViewOpen(true)}
>
<Eye className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ver detalle</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/* VER DETALLE: superadmin, admin, autoridad, manager */}
{['superadmin', 'admin', 'autoridad', 'manager'].includes(
role ?? '',
) && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setViewOpen(true)}
>
<Eye className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Ver detalle</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{/* <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => handleExport(data.id)}
>
<FileDown className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Exportar Excel</p>
</TooltipContent>
</Tooltip>
</TooltipProvider> */}
{/* EDITAR Y ELIMINAR: Solo superadmin y admin */}
{['superadmin', 'admin'].includes(role ?? '') && (
<>
{/* Editar */}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() =>
router.push(`/dashboard/formulario/editar/${data.id}`)
}
>
<Edit className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() =>
router.push(`/dashboard/formulario/editar/${data.id}`)
}
>
<Edit className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Editar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setOpen(true)}
>
<Trash className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Eliminar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
{/* Eliminar */}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
onClick={() => setOpen(true)}
>
<Trash className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Eliminar</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
)}
</div>
</>
);

View File

@@ -3,12 +3,15 @@
import { Button } from '@repo/shadcn/components/ui/button';
import { DataTableSearch } from '@repo/shadcn/table/data-table-search';
import { Plus } from 'lucide-react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useTrainingTableFilters } from './use-training-table-filters';
export default function TrainingTableAction() {
const { searchQuery, setPage, setSearchQuery } = useTrainingTableFilters();
const router = useRouter();
const { data: session } = useSession();
const role = session?.user.role[0]?.rol;
return (
<div className="flex items-center justify-between mt-4 ">
<div className="flex items-center gap-4 flex-grow">
@@ -19,13 +22,17 @@ export default function TrainingTableAction() {
setPage={setPage}
/>
</div>{' '}
<Button
onClick={() => router.push(`/dashboard/formulario/nuevo`)}
size="sm"
>
<Plus className="h-4 w-4" />
<span className="hidden md:inline">Nuevo Registro</span>
</Button>
{['superadmin', 'admin', 'manager', 'coordinators'].includes(
role ?? '',
) && (
<Button
onClick={() => router.push(`/dashboard/formulario/nuevo`)}
size="sm"
>
<Plus className="h-4 w-4" />
<span className="hidden md:inline">Nuevo Registro</span>
</Button>
)}
</div>
);
}