Merge pull request #39 from rede5/Front-back-integracao-task16
fix: ajustado fluxo e geracao de codigos de acesso
This commit is contained in:
commit
991897dcc5
8 changed files with 175 additions and 9 deletions
|
|
@ -146,6 +146,7 @@ func main() {
|
||||||
r.GET("/api/tipos-servicos", tiposServicosHandler.List)
|
r.GET("/api/tipos-servicos", tiposServicosHandler.List)
|
||||||
r.GET("/api/tipos-eventos", tiposEventosHandler.List)
|
r.GET("/api/tipos-eventos", tiposEventosHandler.List)
|
||||||
r.GET("/api/tipos-eventos/:id/precos", tiposEventosHandler.ListPrices)
|
r.GET("/api/tipos-eventos/:id/precos", tiposEventosHandler.ListPrices)
|
||||||
|
r.GET("/api/public/codigos-acesso/verificar", codigosHandler.Verify)
|
||||||
|
|
||||||
// Protected Routes
|
// Protected Routes
|
||||||
api := r.Group("/api")
|
api := r.Group("/api")
|
||||||
|
|
|
||||||
|
|
@ -2135,6 +2135,43 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/public/codigos-acesso/verificar": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"codigos"
|
||||||
|
],
|
||||||
|
"summary": "Verify Access Code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Code",
|
||||||
|
"name": "code",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tipos-eventos": {
|
"/api/tipos-eventos": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
||||||
|
|
@ -2129,6 +2129,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/public/codigos-acesso/verificar": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"codigos"
|
||||||
|
],
|
||||||
|
"summary": "Verify Access Code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Code",
|
||||||
|
"name": "code",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tipos-eventos": {
|
"/api/tipos-eventos": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
||||||
|
|
@ -1874,6 +1874,30 @@ paths:
|
||||||
summary: Update profissional
|
summary: Update profissional
|
||||||
tags:
|
tags:
|
||||||
- profissionais
|
- profissionais
|
||||||
|
/api/public/codigos-acesso/verificar:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Code
|
||||||
|
in: query
|
||||||
|
name: code
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Verify Access Code
|
||||||
|
tags:
|
||||||
|
- codigos
|
||||||
/api/tipos-eventos:
|
/api/tipos-eventos:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,34 @@ func (h *Handler) Delete(c *gin.Context) {
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify godoc
|
||||||
|
// @Summary Verify Access Code
|
||||||
|
// @Tags codigos
|
||||||
|
// @Param code query string true "Code"
|
||||||
|
// @Success 200 {object} map[string]bool
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Router /api/public/codigos-acesso/verificar [get]
|
||||||
|
func (h *Handler) Verify(c *gin.Context) {
|
||||||
|
code := c.Query("code")
|
||||||
|
if code == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "code required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.service.Verify(c.Request.Context(), code)
|
||||||
|
if err != nil {
|
||||||
|
// Distinguish validation error from DB error strictly?
|
||||||
|
// For security, just say invalid.
|
||||||
|
// But service returns specific AppError.
|
||||||
|
if _, ok := err.(*AppError); ok {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"valid": false, "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If check failed (not found etc), return 404 or 400
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"valid": false, "error": "Código inválido"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"valid": true})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,29 @@ func (s *Service) IncrementUse(ctx context.Context, id uuid.UUID) error {
|
||||||
pgUUID.Valid = true
|
pgUUID.Valid = true
|
||||||
return s.q.IncrementCodigoAcessoUso(ctx, pgUUID)
|
return s.q.IncrementCodigoAcessoUso(ctx, pgUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom error for validation
|
||||||
|
type AppError struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AppError) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Verify(ctx context.Context, code string) error {
|
||||||
|
c, err := s.q.GetCodigoAcesso(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
return err // Not found or DB error
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Ativo {
|
||||||
|
return &AppError{Message: "Código inativo"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(c.ExpiraEm.Time) {
|
||||||
|
return &AppError{Message: "Código expirado"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button } from "../components/Button";
|
import { Button } from "../components/Button";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
|
import { verifyAccessCode } from "../services/apiService";
|
||||||
|
|
||||||
interface HomeProps {
|
interface HomeProps {
|
||||||
onEnter: () => void;
|
onEnter: () => void;
|
||||||
|
|
@ -12,26 +13,28 @@ export const Home: React.FC<HomeProps> = ({ onEnter }) => {
|
||||||
const [showProfessionalPrompt, setShowProfessionalPrompt] = useState(false);
|
const [showProfessionalPrompt, setShowProfessionalPrompt] = useState(false);
|
||||||
const [codeError, setCodeError] = useState("");
|
const [codeError, setCodeError] = useState("");
|
||||||
|
|
||||||
// Código mockado - em produção, verificar com o backend
|
|
||||||
const MOCK_ACCESS_CODE = "PHOTUM2025";
|
|
||||||
|
|
||||||
const handleRegisterClick = () => {
|
const handleRegisterClick = () => {
|
||||||
setShowProfessionalPrompt(true);
|
setShowProfessionalPrompt(true);
|
||||||
setAccessCode("");
|
setAccessCode("");
|
||||||
setCodeError("");
|
setCodeError("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVerifyCode = () => {
|
const handleVerifyCode = async () => {
|
||||||
if (accessCode.trim() === "") {
|
if (accessCode.trim() === "") {
|
||||||
setCodeError("Por favor, digite o código de acesso");
|
setCodeError("Por favor, digite o código de acesso");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessCode.toUpperCase() === MOCK_ACCESS_CODE) {
|
try {
|
||||||
setShowAccessCodeModal(false);
|
const res = await verifyAccessCode(accessCode.toUpperCase());
|
||||||
window.location.href = "/cadastro";
|
if (res.data && res.data.valid) {
|
||||||
} else {
|
setShowAccessCodeModal(false);
|
||||||
setCodeError("Código de acesso inválido ou expirado");
|
window.location.href = "/cadastro";
|
||||||
|
} else {
|
||||||
|
setCodeError(res.data?.error || "Código de acesso inválido ou expirado");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setCodeError("Erro ao verificar código");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1059,3 +1059,10 @@ export async function listPassengers(carroId: string, token: string) {
|
||||||
return { data, error: null };
|
return { data, error: null };
|
||||||
} catch (err: any) { return { data: null, error: err.message }; }
|
} catch (err: any) { return { data: null, error: err.message }; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se um código de acesso é válido
|
||||||
|
*/
|
||||||
|
export async function verifyAccessCode(code: string): Promise<ApiResponse<{ valid: boolean; error?: string }>> {
|
||||||
|
return fetchFromBackend<{ valid: boolean; error?: string }>(`/api/public/codigos-acesso/verificar?code=${encodeURIComponent(code)}`);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue