# Frontend Integration Guide - Grant Bonus ML Coins ## Para el Frontend Developer Este documento explica cómo integrar el nuevo endpoint de Grant Bonus ML Coins en el frontend. --- ## 1. API Endpoint ### URL ``` POST /api/v1/teacher/students/:studentId/bonus ``` ### Request ```typescript interface GrantBonusRequest { amount: number; // 1-1000 reason: string; // min 10 caracteres } ``` ### Response ```typescript interface GrantBonusResponse { success: boolean; newBalance: number; message: string; amountGranted: number; reason: string; } ``` --- ## 2. API Client Implementation ### Ubicación sugerida ``` apps/frontend/src/services/api/teacher/bonusCoinsApi.ts ``` ### Implementación ```typescript // apps/frontend/src/services/api/teacher/bonusCoinsApi.ts import { apiClient } from '../apiClient'; import { API_ROUTES } from '@/shared/constants'; export interface GrantBonusRequest { amount: number; reason: string; } export interface GrantBonusResponse { success: boolean; newBalance: number; message: string; amountGranted: number; reason: string; } /** * Otorga bonus de ML Coins a un estudiante * @param studentId - ID del estudiante * @param request - Datos del bonus (amount, reason) */ export async function grantBonusToStudent( studentId: string, request: GrantBonusRequest ): Promise { const response = await apiClient.post( `/teacher/students/${studentId}/bonus`, request ); return response.data; } ``` ### Agregar al barrel export ```typescript // apps/frontend/src/services/api/teacher/index.ts export * from './bonusCoinsApi'; ``` --- ## 3. React Hook ### Ubicación sugerida ``` apps/frontend/src/apps/teacher/hooks/useGrantBonus.ts ``` ### Implementación ```typescript // apps/frontend/src/apps/teacher/hooks/useGrantBonus.ts import { useState } from 'react'; import { grantBonusToStudent, GrantBonusRequest } from '@/services/api/teacher'; import { useToast } from '@/shared/hooks/useToast'; interface UseGrantBonusReturn { grantBonus: (studentId: string, request: GrantBonusRequest) => Promise; isLoading: boolean; error: string | null; success: boolean; } export function useGrantBonus(): UseGrantBonusReturn { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const { showToast } = useToast(); const grantBonus = async (studentId: string, request: GrantBonusRequest) => { setIsLoading(true); setError(null); setSuccess(false); try { const response = await grantBonusToStudent(studentId, request); setSuccess(true); showToast({ type: 'success', message: `${response.amountGranted} ML Coins otorgados exitosamente`, description: `Nuevo balance: ${response.newBalance} ML Coins`, }); // Opcional: Refrescar datos del estudiante aquí // queryClient.invalidateQueries(['student', studentId]); } catch (err: any) { const errorMessage = err.response?.data?.message || 'Error al otorgar bonus'; setError(errorMessage); showToast({ type: 'error', message: 'Error al otorgar bonus', description: errorMessage, }); } finally { setIsLoading(false); } }; return { grantBonus, isLoading, error, success, }; } ``` ### Export en barrel ```typescript // apps/frontend/src/apps/teacher/hooks/index.ts export { useGrantBonus } from './useGrantBonus'; ``` --- ## 4. Componente Modal ### Ubicación sugerida ``` apps/frontend/src/apps/teacher/components/students/GrantBonusModal.tsx ``` ### Implementación Básica ```typescript // apps/frontend/src/apps/teacher/components/students/GrantBonusModal.tsx import React, { useState } from 'react'; import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, FormControl, FormLabel, Input, Textarea, NumberInput, NumberInputField, FormErrorMessage, } from '@chakra-ui/react'; import { useGrantBonus } from '@/apps/teacher/hooks/useGrantBonus'; interface GrantBonusModalProps { isOpen: boolean; onClose: () => void; studentId: string; studentName: string; } export const GrantBonusModal: React.FC = ({ isOpen, onClose, studentId, studentName, }) => { const [amount, setAmount] = useState(50); const [reason, setReason] = useState(''); const { grantBonus, isLoading, error } = useGrantBonus(); const handleSubmit = async () => { await grantBonus(studentId, { amount, reason }); // Si fue exitoso, limpiar y cerrar if (!error) { setAmount(50); setReason(''); onClose(); } }; const isReasonValid = reason.length >= 10; const isAmountValid = amount >= 1 && amount <= 1000; const isFormValid = isReasonValid && isAmountValid; return ( Otorgar Bonus a {studentName} 0}> Cantidad de ML Coins setAmount(val)} min={1} max={1000} > {!isAmountValid && amount > 0 && ( Debe ser entre 1 y 1000 ML Coins )} 0}> Razón del bonus