feat: habilita edição de perfil para clientes e corrige carga de dados
Backend: - Adiciona endpoint `PUT /api/me` para permitir atualização de dados do usuário logado. - Implementa query `UpdateCadastroCliente` e função de serviço [UpdateClientData]para persistir alterações de clientes. - Atualiza handlers [Me], [Login] e [ListPending] para incluir e mapear corretamente campos de cliente (CPF, Endereço, Telefone). - Corrige mapeamento do campo `phone` na struct de resposta do usuário. Frontend: - Habilita o formulário de edição em [Profile.tsx] para usuários do tipo 'CLIENTE' (Event Owner). - Adiciona função [updateUserProfile] em [apiService.ts] para consumir o novo endpoint. - Atualiza [AuthContext] para persistir campos do cliente (CPF, Endereço, etc.) durante a restauração de sessão ([restoreSession], corrigindo o bug de perfil vazio ao recarregar a página. - Padroniza envio de dados no Registro e Aprovação para usar `snake_case` (ex: `cpf_cnpj`, `professional_type`). - Atualiza tipos em [types.ts] para incluir campos de endereço e documentos.
This commit is contained in:
parent
788e0dca70
commit
9c6ee3afdb
16 changed files with 981 additions and 168 deletions
|
|
@ -173,6 +173,7 @@ func main() {
|
||||||
api.Use(auth.AuthMiddleware(cfg))
|
api.Use(auth.AuthMiddleware(cfg))
|
||||||
{
|
{
|
||||||
api.GET("/me", authHandler.Me)
|
api.GET("/me", authHandler.Me)
|
||||||
|
api.PUT("/me", authHandler.UpdateMe)
|
||||||
|
|
||||||
profGroup := api.Group("/profissionais")
|
profGroup := api.Group("/profissionais")
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,20 @@ func (h *Handler) GetUploadURL(c *gin.Context) {
|
||||||
type registerRequest struct {
|
type registerRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Senha string `json:"senha" binding:"required,min=6"`
|
Senha string `json:"senha" binding:"required,min=6"`
|
||||||
|
Role string `json:"role" binding:"required"`
|
||||||
Nome string `json:"nome" binding:"required"`
|
Nome string `json:"nome" binding:"required"`
|
||||||
Telefone string `json:"telefone"`
|
Telefone string `json:"telefone"`
|
||||||
Role string `json:"role" binding:"required"`
|
TipoProfissional string `json:"professional_type"`
|
||||||
EmpresaID string `json:"empresa_id"`
|
EmpresaID string `json:"empresa_id"`
|
||||||
TipoProfissional string `json:"tipo_profissional"` // New field
|
Regiao string `json:"regiao"`
|
||||||
Regiao string `json:"regiao"` // Optional: for AdminCreateUser override
|
CpfCnpj string `json:"cpf_cnpj"`
|
||||||
|
Cep string `json:"cep"`
|
||||||
|
Endereco string `json:"endereco"`
|
||||||
|
Numero string `json:"numero"`
|
||||||
|
Complemento string `json:"complemento"`
|
||||||
|
Bairro string `json:"bairro"`
|
||||||
|
Cidade string `json:"cidade"`
|
||||||
|
Estado string `json:"estado"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register godoc
|
// Register godoc
|
||||||
|
|
@ -105,7 +113,7 @@ func (h *Handler) Register(c *gin.Context) {
|
||||||
regiao = "SP"
|
regiao = "SP"
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, req.TipoProfissional, empresaIDPtr, profData, regiao)
|
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, req.TipoProfissional, empresaIDPtr, profData, regiao, req.CpfCnpj, req.Cep, req.Endereco, req.Numero, req.Complemento, req.Bairro, req.Cidade, req.Estado)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
||||||
|
|
@ -177,6 +185,14 @@ type userResponse struct {
|
||||||
CompanyID string `json:"company_id,omitempty"`
|
CompanyID string `json:"company_id,omitempty"`
|
||||||
CompanyName string `json:"company_name,omitempty"`
|
CompanyName string `json:"company_name,omitempty"`
|
||||||
AllowedRegions []string `json:"allowed_regions"`
|
AllowedRegions []string `json:"allowed_regions"`
|
||||||
|
CpfCnpj string `json:"cpf_cnpj,omitempty"`
|
||||||
|
Cep string `json:"cep,omitempty"`
|
||||||
|
Endereco string `json:"endereco,omitempty"`
|
||||||
|
Numero string `json:"numero,omitempty"`
|
||||||
|
Complemento string `json:"complemento,omitempty"`
|
||||||
|
Bairro string `json:"bairro,omitempty"`
|
||||||
|
Cidade string `json:"cidade,omitempty"`
|
||||||
|
Estado string `json:"estado,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login godoc
|
// Login godoc
|
||||||
|
|
@ -233,22 +249,66 @@ func (h *Handler) Login(c *gin.Context) {
|
||||||
companyName = user.EmpresaNome.String
|
companyName = user.EmpresaNome.String
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := loginResponse{
|
// Prepare response
|
||||||
AccessToken: tokenPair.AccessToken,
|
uResp := userResponse{
|
||||||
ExpiresAt: "2025-...", // logic to calculate if needed, or remove field
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
User: userResponse{
|
Email: user.Email,
|
||||||
ID: uuid.UUID(user.ID.Bytes).String(),
|
Role: user.Role,
|
||||||
Email: user.Email,
|
Ativo: user.Ativo, // Added this back from original
|
||||||
Role: user.Role,
|
Name: user.Nome,
|
||||||
Ativo: user.Ativo,
|
CompanyID: companyID,
|
||||||
Name: user.Nome,
|
CompanyName: companyName,
|
||||||
Phone: user.Whatsapp,
|
AllowedRegions: user.RegioesPermitidas,
|
||||||
CompanyID: companyID,
|
Phone: "",
|
||||||
CompanyName: companyName,
|
|
||||||
AllowedRegions: user.RegioesPermitidas,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to get phone from profData or Client Data
|
||||||
|
if user.Role == "PHOTOGRAPHER" && profData != nil {
|
||||||
|
if profData.Whatsapp.Valid {
|
||||||
|
uResp.Phone = profData.Whatsapp.String
|
||||||
|
}
|
||||||
|
} else if user.Role == "EVENT_OWNER" {
|
||||||
|
// Fetch Client Data
|
||||||
|
clientData, err := h.service.GetClientData(c.Request.Context(), uResp.ID)
|
||||||
|
if err == nil && clientData != nil {
|
||||||
|
if clientData.Telefone.Valid {
|
||||||
|
uResp.Phone = clientData.Telefone.String
|
||||||
|
}
|
||||||
|
if clientData.CpfCnpj.Valid {
|
||||||
|
uResp.CpfCnpj = clientData.CpfCnpj.String
|
||||||
|
}
|
||||||
|
if clientData.Cep.Valid {
|
||||||
|
uResp.Cep = clientData.Cep.String
|
||||||
|
}
|
||||||
|
if clientData.Endereco.Valid {
|
||||||
|
uResp.Endereco = clientData.Endereco.String
|
||||||
|
}
|
||||||
|
if clientData.Numero.Valid {
|
||||||
|
uResp.Numero = clientData.Numero.String
|
||||||
|
}
|
||||||
|
if clientData.Complemento.Valid {
|
||||||
|
uResp.Complemento = clientData.Complemento.String
|
||||||
|
}
|
||||||
|
if clientData.Bairro.Valid {
|
||||||
|
uResp.Bairro = clientData.Bairro.String
|
||||||
|
}
|
||||||
|
if clientData.Cidade.Valid {
|
||||||
|
uResp.Cidade = clientData.Cidade.String
|
||||||
|
}
|
||||||
|
if clientData.Estado.Valid {
|
||||||
|
uResp.Estado = clientData.Estado.String
|
||||||
|
}
|
||||||
|
// Use client name if available ?? Or user name is fine.
|
||||||
|
// Usually user.Name comes from `usuarios` table, but `cadastro_clientes` also has nome.
|
||||||
|
// Let's stick to user.Name for consistency, usually they should be same.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := loginResponse{
|
||||||
|
AccessToken: tokenPair.AccessToken,
|
||||||
|
ExpiresAt: "2025-...",
|
||||||
|
User: uResp,
|
||||||
|
}
|
||||||
if profData != nil {
|
if profData != nil {
|
||||||
resp.Profissional = map[string]interface{}{
|
resp.Profissional = map[string]interface{}{
|
||||||
"id": uuid.UUID(profData.ID.Bytes).String(),
|
"id": uuid.UUID(profData.ID.Bytes).String(),
|
||||||
|
|
@ -368,38 +428,76 @@ func (h *Handler) Me(c *gin.Context) {
|
||||||
allowedRegions = append(allowedRegions, user.RegioesPermitidas...)
|
allowedRegions = append(allowedRegions, user.RegioesPermitidas...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uResp := userResponse{
|
||||||
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
||||||
|
Email: user.Email,
|
||||||
|
Role: user.Role,
|
||||||
|
Ativo: user.Ativo,
|
||||||
|
Name: user.Nome,
|
||||||
|
Phone: user.Whatsapp, // Default to user.Whatsapp
|
||||||
|
CompanyName: empresaNome,
|
||||||
|
CompanyID: empresaID,
|
||||||
|
AllowedRegions: allowedRegions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Role == "EVENT_OWNER" {
|
||||||
|
clientData, err := h.service.GetClientData(c.Request.Context(), uResp.ID)
|
||||||
|
if err == nil && clientData != nil {
|
||||||
|
if clientData.Telefone.Valid {
|
||||||
|
uResp.Phone = clientData.Telefone.String
|
||||||
|
}
|
||||||
|
if clientData.CpfCnpj.Valid {
|
||||||
|
uResp.CpfCnpj = clientData.CpfCnpj.String
|
||||||
|
}
|
||||||
|
if clientData.Cep.Valid {
|
||||||
|
uResp.Cep = clientData.Cep.String
|
||||||
|
}
|
||||||
|
if clientData.Endereco.Valid {
|
||||||
|
uResp.Endereco = clientData.Endereco.String
|
||||||
|
}
|
||||||
|
if clientData.Numero.Valid {
|
||||||
|
uResp.Numero = clientData.Numero.String
|
||||||
|
}
|
||||||
|
if clientData.Complemento.Valid {
|
||||||
|
uResp.Complemento = clientData.Complemento.String
|
||||||
|
}
|
||||||
|
if clientData.Bairro.Valid {
|
||||||
|
uResp.Bairro = clientData.Bairro.String
|
||||||
|
}
|
||||||
|
if clientData.Cidade.Valid {
|
||||||
|
uResp.Cidade = clientData.Cidade.String
|
||||||
|
}
|
||||||
|
if clientData.Estado.Valid {
|
||||||
|
uResp.Estado = clientData.Estado.String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp := loginResponse{
|
resp := loginResponse{
|
||||||
User: userResponse{
|
User: uResp,
|
||||||
ID: uuid.UUID(user.ID.Bytes).String(),
|
|
||||||
Email: user.Email,
|
|
||||||
Role: user.Role,
|
|
||||||
Ativo: user.Ativo,
|
|
||||||
Name: user.Nome,
|
|
||||||
Phone: user.Whatsapp,
|
|
||||||
CompanyName: empresaNome,
|
|
||||||
CompanyID: empresaID,
|
|
||||||
AllowedRegions: allowedRegions,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Role == "PHOTOGRAPHER" || user.Role == "BUSINESS_OWNER" {
|
if user.Role == "PHOTOGRAPHER" || user.Role == "BUSINESS_OWNER" {
|
||||||
regiao := c.GetString("regiao")
|
regiao := c.GetString("regiao")
|
||||||
// If regiao is empty, we might skip fetching professional data or default?
|
if regiao == "" {
|
||||||
// For now if empty, GetProfessionalByUserID with valid=true and string="" will likely fail or return empty?
|
regiao = c.GetHeader("x-regiao")
|
||||||
// Queries check regiao = $2. If regiao is "", and DB has "SP", it won't match.
|
}
|
||||||
// So user needs to send header for Me to see pro data.
|
|
||||||
if regiao != "" {
|
profData, err := h.service.GetProfessionalByUserID(c.Request.Context(), uuid.UUID(user.ID.Bytes).String())
|
||||||
profData, err := h.service.GetProfessionalByUserID(c.Request.Context(), uuid.UUID(user.ID.Bytes).String())
|
if err == nil && profData != nil {
|
||||||
if err == nil && profData != nil {
|
// Update phone from professional data if valid
|
||||||
resp.Profissional = map[string]interface{}{
|
if profData.Whatsapp.Valid {
|
||||||
"id": uuid.UUID(profData.ID.Bytes).String(),
|
resp.User.Phone = profData.Whatsapp.String
|
||||||
"nome": profData.Nome,
|
}
|
||||||
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
|
|
||||||
"funcao_profissional": "", // Deprecated
|
resp.Profissional = map[string]interface{}{
|
||||||
"functions": profData.Functions,
|
"id": uuid.UUID(profData.ID.Bytes).String(),
|
||||||
"equipamentos": profData.Equipamentos.String,
|
"nome": profData.Nome,
|
||||||
"avatar_url": profData.AvatarUrl.String,
|
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
|
||||||
}
|
"funcao_profissional": "", // Deprecated
|
||||||
|
"functions": profData.Functions,
|
||||||
|
"equipamentos": profData.Equipamentos.String,
|
||||||
|
"avatar_url": profData.AvatarUrl.String,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -407,6 +505,75 @@ func (h *Handler) Me(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type updateMeRequest struct {
|
||||||
|
Nome string `json:"name"`
|
||||||
|
Telefone string `json:"phone"`
|
||||||
|
CpfCnpj string `json:"cpf_cnpj"`
|
||||||
|
Cep string `json:"cep"`
|
||||||
|
Endereco string `json:"endereco"`
|
||||||
|
Numero string `json:"numero"`
|
||||||
|
Complemento string `json:"complemento"`
|
||||||
|
Bairro string `json:"bairro"`
|
||||||
|
Cidade string `json:"cidade"`
|
||||||
|
Estado string `json:"estado"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMe godoc
|
||||||
|
// @Summary Update current user profile
|
||||||
|
// @Description Update profile information (Name, Phone, Address). Currently for EVENT_OWNER.
|
||||||
|
// @Tags auth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} map[string]string
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 500 {object} map[string]string
|
||||||
|
// @Router /api/me [put]
|
||||||
|
func (h *Handler) UpdateMe(c *gin.Context) {
|
||||||
|
userID, exists := c.Get("userID")
|
||||||
|
if !exists {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req updateMeRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we assume this is mostly for Clients (EVENT_OWNER)
|
||||||
|
// based on the fields provided (address, cpf, etc).
|
||||||
|
// Future: Check role and call appropriate service if needed.
|
||||||
|
// But UpdateClientData uses "UpdateCadastroCliente" which is linked to user_id.
|
||||||
|
// If the user does not have a client record, it might fail or do nothing if query uses WHERE exists?
|
||||||
|
// The query uses UPDATE, so if no row, no update.
|
||||||
|
|
||||||
|
// We should probably check role or just try to update.
|
||||||
|
// But to be safe, let's just call UpdateClientData.
|
||||||
|
// If the user is a Photographer, they should use the /profissionais/me PUT (if exists) or similar.
|
||||||
|
// But wait, the user complaint is about Clients.
|
||||||
|
|
||||||
|
err := h.service.UpdateClientData(c.Request.Context(), userID.(string), UpdateClientInput{
|
||||||
|
Nome: req.Nome,
|
||||||
|
Telefone: req.Telefone,
|
||||||
|
CpfCnpj: req.CpfCnpj,
|
||||||
|
Cep: req.Cep,
|
||||||
|
Endereco: req.Endereco,
|
||||||
|
Numero: req.Numero,
|
||||||
|
Complemento: req.Complemento,
|
||||||
|
Bairro: req.Bairro,
|
||||||
|
Cidade: req.Cidade,
|
||||||
|
Estado: req.Estado,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update profile: " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "profile updated successfully"})
|
||||||
|
}
|
||||||
|
|
||||||
// ListPending godoc
|
// ListPending godoc
|
||||||
// @Summary List pending users
|
// @Summary List pending users
|
||||||
// @Description List users with ativo=false
|
// @Description List users with ativo=false
|
||||||
|
|
@ -525,7 +692,7 @@ func (h *Handler) AdminCreateUser(c *gin.Context) {
|
||||||
// Let's assume header is present or default.
|
// Let's assume header is present or default.
|
||||||
regiao = "SP" // Default for now if missing? Or error?
|
regiao = "SP" // Default for now if missing? Or error?
|
||||||
}
|
}
|
||||||
user, err := h.service.AdminCreateUser(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.TipoProfissional, true, regiao)
|
user, err := h.service.AdminCreateUser(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.TipoProfissional, true, regiao, req.EmpresaID, req.Telefone, req.CpfCnpj, req.Cep, req.Endereco, req.Numero, req.Complemento, req.Bairro, req.Cidade, req.Estado)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
|
||||||
|
|
@ -620,7 +787,11 @@ func (h *Handler) DeleteUser(c *gin.Context) {
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Router /api/admin/users [get]
|
// @Router /api/admin/users [get]
|
||||||
func (h *Handler) ListUsers(c *gin.Context) {
|
func (h *Handler) ListUsers(c *gin.Context) {
|
||||||
users, err := h.service.ListUsers(c.Request.Context())
|
regiao := c.GetString("regiao")
|
||||||
|
if regiao == "" {
|
||||||
|
regiao = c.GetHeader("x-regiao")
|
||||||
|
}
|
||||||
|
users, err := h.service.ListUsers(c.Request.Context(), regiao)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"photum-backend/internal/profissionais"
|
"photum-backend/internal/profissionais"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
@ -46,7 +47,7 @@ func NewService(queries *generated.Queries, profissionaisService *profissionais.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefone, tipoProfissional string, empresaID *string, profissionalData *profissionais.CreateProfissionalInput, regiao string) (*generated.Usuario, error) {
|
func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefone, tipoProfissional string, empresaID *string, profissionalData *profissionais.CreateProfissionalInput, regiao, cpfCnpj, cep, endereco, numero, complemento, bairro, cidade, estado string) (*generated.Usuario, error) {
|
||||||
// Hash password
|
// Hash password
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -93,21 +94,27 @@ func (s *Service) Register(ctx context.Context, email, senha, role, nome, telefo
|
||||||
|
|
||||||
// If role is 'EVENT_OWNER', create client profile
|
// If role is 'EVENT_OWNER', create client profile
|
||||||
if role == RoleEventOwner {
|
if role == RoleEventOwner {
|
||||||
userID := user.ID
|
|
||||||
var empID pgtype.UUID
|
var empID pgtype.UUID
|
||||||
if empresaID != nil && *empresaID != "" {
|
if empresaID != nil && *empresaID != "" {
|
||||||
parsedEmpID, err := uuid.Parse(*empresaID)
|
parsedEmpID, err := uuid.Parse(*empresaID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
empID.Bytes = parsedEmpID
|
empID = pgtype.UUID{Bytes: parsedEmpID, Valid: true}
|
||||||
empID.Valid = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.queries.CreateCadastroCliente(ctx, generated.CreateCadastroClienteParams{
|
_, err = s.queries.CreateCadastroCliente(ctx, generated.CreateCadastroClienteParams{
|
||||||
UsuarioID: userID,
|
UsuarioID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
|
||||||
EmpresaID: empID,
|
EmpresaID: empID,
|
||||||
Nome: pgtype.Text{String: nome, Valid: nome != ""},
|
Nome: pgtype.Text{String: nome, Valid: true},
|
||||||
Telefone: pgtype.Text{String: telefone, Valid: telefone != ""},
|
Telefone: pgtype.Text{String: telefone, Valid: telefone != ""},
|
||||||
|
CpfCnpj: toPgText(&cpfCnpj),
|
||||||
|
Cep: toPgText(&cep),
|
||||||
|
Endereco: toPgText(&endereco),
|
||||||
|
Numero: toPgText(&numero),
|
||||||
|
Complemento: toPgText(&complemento),
|
||||||
|
Bairro: toPgText(&bairro),
|
||||||
|
Cidade: toPgText(&cidade),
|
||||||
|
Estado: toPgText(&estado),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
||||||
|
|
@ -218,7 +225,7 @@ func (s *Service) ApproveUser(ctx context.Context, id string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome, tipoProfissional string, ativo bool, regiao string) (*generated.Usuario, error) {
|
func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome, tipoProfissional string, ativo bool, regiao string, empresaID string, telefone, cpfCnpj, cep, endereco, numero, complemento, bairro, cidade, estado string) (*generated.Usuario, error) {
|
||||||
// Hash password
|
// Hash password
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(senha), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -246,14 +253,10 @@ func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome,
|
||||||
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Update the user struct in memory too (important for the return value?)
|
|
||||||
// user.RegioesPermitidas = []string{regiao} // Generated struct might differ, but return value is pointer to user.
|
|
||||||
// Since we return &user, and user is local struct from CreateUsuario, it has empty RegioesPermitidas.
|
|
||||||
// It's better to manually updating it if downstream depends on it, but usually ID/Email is enough.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ativo {
|
if ativo {
|
||||||
// Approve user immediately (already active=true by default in DB? No, default false)
|
// Approve user immediately
|
||||||
err = s.ApproveUser(ctx, uuid.UUID(user.ID.Bytes).String())
|
err = s.ApproveUser(ctx, uuid.UUID(user.ID.Bytes).String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
||||||
|
|
@ -262,6 +265,33 @@ func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome,
|
||||||
user.Ativo = true
|
user.Ativo = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Link Company if EVENT_OWNER
|
||||||
|
if role == RoleEventOwner && empresaID != "" {
|
||||||
|
empUuid, err := uuid.Parse(empresaID)
|
||||||
|
if err == nil {
|
||||||
|
_, err = s.queries.CreateCadastroCliente(ctx, generated.CreateCadastroClienteParams{
|
||||||
|
UsuarioID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
|
||||||
|
EmpresaID: pgtype.UUID{Bytes: empUuid, Valid: true},
|
||||||
|
Nome: pgtype.Text{String: nome, Valid: true},
|
||||||
|
Telefone: toPgText(&telefone),
|
||||||
|
CpfCnpj: toPgText(&cpfCnpj),
|
||||||
|
Cep: toPgText(&cep),
|
||||||
|
Endereco: toPgText(&endereco),
|
||||||
|
Numero: toPgText(&numero),
|
||||||
|
Complemento: toPgText(&complemento),
|
||||||
|
Bairro: toPgText(&bairro),
|
||||||
|
Cidade: toPgText(&cidade),
|
||||||
|
Estado: toPgText(&estado),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// Log error but maybe don't fail user creation?
|
||||||
|
// Ideally rollback
|
||||||
|
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create professional profile if applicable
|
// Create professional profile if applicable
|
||||||
if role == RolePhotographer || role == RoleBusinessOwner {
|
if role == RolePhotographer || role == RoleBusinessOwner {
|
||||||
var funcaoID string
|
var funcaoID string
|
||||||
|
|
@ -285,22 +315,11 @@ func (s *Service) AdminCreateUser(ctx context.Context, email, senha, role, nome,
|
||||||
Nome: nome,
|
Nome: nome,
|
||||||
Email: &email,
|
Email: &email,
|
||||||
FuncaoProfissionalID: funcaoID,
|
FuncaoProfissionalID: funcaoID,
|
||||||
// Add default whatsapp if user has one? User struct doesn't have phone here passed in args,
|
|
||||||
// but we can pass it if we update signature or just leave empty.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the professional
|
// Create the professional
|
||||||
_, err = s.profissionaisService.Create(ctx, uuid.UUID(user.ID.Bytes).String(), profInput, regiao)
|
_, err = s.profissionaisService.Create(ctx, uuid.UUID(user.ID.Bytes).String(), profInput, regiao)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log error but don't fail user creation?
|
|
||||||
// Better to log. Backend logs not setup here.
|
|
||||||
// Just continue for now, or return error?
|
|
||||||
// If we fail here, user exists but has no profile.
|
|
||||||
// Ideally we should delete user or return error.
|
|
||||||
// Let's log and ignore for now to avoid breaking legacy flows if any.
|
|
||||||
// Actually, if this fails, the bug persists. Best to return error.
|
|
||||||
// But since we already committed user, we should probably return error so client knows.
|
|
||||||
|
|
||||||
// Try to delete user to rollback
|
// Try to delete user to rollback
|
||||||
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
_ = s.queries.DeleteUsuario(ctx, user.ID)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -367,20 +386,25 @@ func (s *Service) EnsureDemoUsers(ctx context.Context) error {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("[DEBUG] Creating User %s\n", u.Email)
|
fmt.Printf("[DEBUG] Creating User %s\n", u.Email)
|
||||||
// User not found (or error), try to create
|
// Create if not exists
|
||||||
// Note: We use "SP" as default regiao for professional creation
|
if errors.Is(err, pgx.ErrNoRows) { // Only create if user not found
|
||||||
user, err := s.AdminCreateUser(ctx, u.Email, "123456", u.Role, u.Name, "", true, "SP")
|
fmt.Printf("Creating demo user: %s (%s)\n", u.Email, u.Role)
|
||||||
if err != nil {
|
// Pass empty strings for new client fields for demo users
|
||||||
fmt.Printf("[DEBUG] Error creating user %s: %v\n", u.Email, err)
|
user, err := s.AdminCreateUser(ctx, u.Email, "123456", u.Role, u.Name, "", true, regions[0], "", "", "", "", "", "", "", "", "", "")
|
||||||
return err
|
if err != nil {
|
||||||
}
|
fmt.Printf("Error creating demo user %s: %v\n", u.Email, err)
|
||||||
// Update to include specific regions
|
return err
|
||||||
err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
|
}
|
||||||
ID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
|
// Update to include specific regions
|
||||||
RegioesPermitidas: regions,
|
err = s.queries.UpdateUsuarioRegions(ctx, generated.UpdateUsuarioRegionsParams{
|
||||||
})
|
ID: pgtype.UUID{Bytes: user.ID.Bytes, Valid: true},
|
||||||
if err != nil {
|
RegioesPermitidas: regions,
|
||||||
fmt.Printf("[DEBUG] Error updating regions for new user %s: %v\n", u.Email, err)
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[DEBUG] Error updating regions for new user %s: %v\n", u.Email, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else { // If it's another error, return it
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -422,8 +446,46 @@ func (s *Service) EnsureDemoUsers(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListUsers(ctx context.Context) ([]generated.ListAllUsuariosRow, error) {
|
type UpdateClientInput struct {
|
||||||
return s.queries.ListAllUsuarios(ctx)
|
Nome string
|
||||||
|
Telefone string
|
||||||
|
CpfCnpj string
|
||||||
|
Cep string
|
||||||
|
Endereco string
|
||||||
|
Numero string
|
||||||
|
Complemento string
|
||||||
|
Bairro string
|
||||||
|
Cidade string
|
||||||
|
Estado string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateClientData(ctx context.Context, userID string, input UpdateClientInput) error {
|
||||||
|
uid, err := uuid.Parse(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare params
|
||||||
|
params := generated.UpdateCadastroClienteParams{
|
||||||
|
UsuarioID: pgtype.UUID{Bytes: uid, Valid: true},
|
||||||
|
Nome: pgtype.Text{String: input.Nome, Valid: input.Nome != ""},
|
||||||
|
Telefone: pgtype.Text{String: input.Telefone, Valid: input.Telefone != ""},
|
||||||
|
CpfCnpj: pgtype.Text{String: input.CpfCnpj, Valid: input.CpfCnpj != ""},
|
||||||
|
Cep: pgtype.Text{String: input.Cep, Valid: input.Cep != ""},
|
||||||
|
Endereco: pgtype.Text{String: input.Endereco, Valid: input.Endereco != ""},
|
||||||
|
Numero: pgtype.Text{String: input.Numero, Valid: input.Numero != ""},
|
||||||
|
Complemento: pgtype.Text{String: input.Complemento, Valid: input.Complemento != ""},
|
||||||
|
Bairro: pgtype.Text{String: input.Bairro, Valid: input.Bairro != ""},
|
||||||
|
Cidade: pgtype.Text{String: input.Cidade, Valid: input.Cidade != ""},
|
||||||
|
Estado: pgtype.Text{String: input.Estado, Valid: input.Estado != ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.queries.UpdateCadastroCliente(ctx, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListUsers(ctx context.Context, regiao string) ([]generated.ListAllUsuariosRow, error) {
|
||||||
|
return s.queries.ListAllUsuarios(ctx, regiao)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetUser(ctx context.Context, id string) (*generated.GetUsuarioByIDRow, error) {
|
func (s *Service) GetUser(ctx context.Context, id string) (*generated.GetUsuarioByIDRow, error) {
|
||||||
|
|
@ -458,6 +520,22 @@ func (s *Service) GetProfessionalByUserID(ctx context.Context, userID string) (*
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetClientData(ctx context.Context, userID string) (*generated.CadastroCliente, error) {
|
||||||
|
parsedUUID, err := uuid.Parse(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pgID pgtype.UUID
|
||||||
|
pgID.Bytes = parsedUUID
|
||||||
|
pgID.Valid = true
|
||||||
|
|
||||||
|
c, err := s.queries.GetCadastroClienteByUsuarioID(ctx, pgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func toPgText(s *string) pgtype.Text {
|
func toPgText(s *string) pgtype.Text {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return pgtype.Text{Valid: false}
|
return pgtype.Text{Valid: false}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,14 @@ type CadastroCliente struct {
|
||||||
Telefone pgtype.Text `json:"telefone"`
|
Telefone pgtype.Text `json:"telefone"`
|
||||||
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
CriadoEm pgtype.Timestamptz `json:"criado_em"`
|
||||||
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
AtualizadoEm pgtype.Timestamptz `json:"atualizado_em"`
|
||||||
|
CpfCnpj pgtype.Text `json:"cpf_cnpj"`
|
||||||
|
Cep pgtype.Text `json:"cep"`
|
||||||
|
Endereco pgtype.Text `json:"endereco"`
|
||||||
|
Numero pgtype.Text `json:"numero"`
|
||||||
|
Complemento pgtype.Text `json:"complemento"`
|
||||||
|
Bairro pgtype.Text `json:"bairro"`
|
||||||
|
Cidade pgtype.Text `json:"cidade"`
|
||||||
|
Estado pgtype.Text `json:"estado"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CadastroFot struct {
|
type CadastroFot struct {
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,37 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const createCadastroCliente = `-- name: CreateCadastroCliente :one
|
const createCadastroCliente = `-- name: CreateCadastroCliente :one
|
||||||
INSERT INTO cadastro_clientes (usuario_id, empresa_id, nome, telefone)
|
INSERT INTO cadastro_clientes (
|
||||||
VALUES ($1, $2, $3, $4)
|
usuario_id,
|
||||||
RETURNING id, usuario_id, empresa_id, nome, telefone, criado_em, atualizado_em
|
empresa_id,
|
||||||
|
nome,
|
||||||
|
telefone,
|
||||||
|
cpf_cnpj,
|
||||||
|
cep,
|
||||||
|
endereco,
|
||||||
|
numero,
|
||||||
|
complemento,
|
||||||
|
bairro,
|
||||||
|
cidade,
|
||||||
|
estado
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
|
RETURNING id, usuario_id, empresa_id, nome, telefone, criado_em, atualizado_em, cpf_cnpj, cep, endereco, numero, complemento, bairro, cidade, estado
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateCadastroClienteParams struct {
|
type CreateCadastroClienteParams struct {
|
||||||
UsuarioID pgtype.UUID `json:"usuario_id"`
|
UsuarioID pgtype.UUID `json:"usuario_id"`
|
||||||
EmpresaID pgtype.UUID `json:"empresa_id"`
|
EmpresaID pgtype.UUID `json:"empresa_id"`
|
||||||
Nome pgtype.Text `json:"nome"`
|
Nome pgtype.Text `json:"nome"`
|
||||||
Telefone pgtype.Text `json:"telefone"`
|
Telefone pgtype.Text `json:"telefone"`
|
||||||
|
CpfCnpj pgtype.Text `json:"cpf_cnpj"`
|
||||||
|
Cep pgtype.Text `json:"cep"`
|
||||||
|
Endereco pgtype.Text `json:"endereco"`
|
||||||
|
Numero pgtype.Text `json:"numero"`
|
||||||
|
Complemento pgtype.Text `json:"complemento"`
|
||||||
|
Bairro pgtype.Text `json:"bairro"`
|
||||||
|
Cidade pgtype.Text `json:"cidade"`
|
||||||
|
Estado pgtype.Text `json:"estado"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateCadastroCliente(ctx context.Context, arg CreateCadastroClienteParams) (CadastroCliente, error) {
|
func (q *Queries) CreateCadastroCliente(ctx context.Context, arg CreateCadastroClienteParams) (CadastroCliente, error) {
|
||||||
|
|
@ -30,6 +51,14 @@ func (q *Queries) CreateCadastroCliente(ctx context.Context, arg CreateCadastroC
|
||||||
arg.EmpresaID,
|
arg.EmpresaID,
|
||||||
arg.Nome,
|
arg.Nome,
|
||||||
arg.Telefone,
|
arg.Telefone,
|
||||||
|
arg.CpfCnpj,
|
||||||
|
arg.Cep,
|
||||||
|
arg.Endereco,
|
||||||
|
arg.Numero,
|
||||||
|
arg.Complemento,
|
||||||
|
arg.Bairro,
|
||||||
|
arg.Cidade,
|
||||||
|
arg.Estado,
|
||||||
)
|
)
|
||||||
var i CadastroCliente
|
var i CadastroCliente
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -40,6 +69,14 @@ func (q *Queries) CreateCadastroCliente(ctx context.Context, arg CreateCadastroC
|
||||||
&i.Telefone,
|
&i.Telefone,
|
||||||
&i.CriadoEm,
|
&i.CriadoEm,
|
||||||
&i.AtualizadoEm,
|
&i.AtualizadoEm,
|
||||||
|
&i.CpfCnpj,
|
||||||
|
&i.Cep,
|
||||||
|
&i.Endereco,
|
||||||
|
&i.Numero,
|
||||||
|
&i.Complemento,
|
||||||
|
&i.Bairro,
|
||||||
|
&i.Cidade,
|
||||||
|
&i.Estado,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +128,34 @@ func (q *Queries) DeleteUsuario(ctx context.Context, id pgtype.UUID) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCadastroClienteByUsuarioID = `-- name: GetCadastroClienteByUsuarioID :one
|
||||||
|
SELECT id, usuario_id, empresa_id, nome, telefone, criado_em, atualizado_em, cpf_cnpj, cep, endereco, numero, complemento, bairro, cidade, estado FROM cadastro_clientes
|
||||||
|
WHERE usuario_id = $1 LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCadastroClienteByUsuarioID(ctx context.Context, usuarioID pgtype.UUID) (CadastroCliente, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getCadastroClienteByUsuarioID, usuarioID)
|
||||||
|
var i CadastroCliente
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UsuarioID,
|
||||||
|
&i.EmpresaID,
|
||||||
|
&i.Nome,
|
||||||
|
&i.Telefone,
|
||||||
|
&i.CriadoEm,
|
||||||
|
&i.AtualizadoEm,
|
||||||
|
&i.CpfCnpj,
|
||||||
|
&i.Cep,
|
||||||
|
&i.Endereco,
|
||||||
|
&i.Numero,
|
||||||
|
&i.Complemento,
|
||||||
|
&i.Bairro,
|
||||||
|
&i.Cidade,
|
||||||
|
&i.Estado,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
const getUsuarioByEmail = `-- name: GetUsuarioByEmail :one
|
||||||
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em, u.regioes_permitidas,
|
SELECT u.id, u.email, u.senha_hash, u.role, u.tipo_profissional, u.ativo, u.criado_em, u.atualizado_em, u.regioes_permitidas,
|
||||||
COALESCE(cp.nome, cc.nome, '') as nome,
|
COALESCE(cp.nome, cc.nome, '') as nome,
|
||||||
|
|
@ -201,6 +266,7 @@ FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
||||||
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
||||||
|
WHERE $1::text = ANY(u.regioes_permitidas)
|
||||||
ORDER BY u.criado_em DESC
|
ORDER BY u.criado_em DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -219,8 +285,8 @@ type ListAllUsuariosRow struct {
|
||||||
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
EmpresaNome pgtype.Text `json:"empresa_nome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListAllUsuarios(ctx context.Context) ([]ListAllUsuariosRow, error) {
|
func (q *Queries) ListAllUsuarios(ctx context.Context, dollar_1 string) ([]ListAllUsuariosRow, error) {
|
||||||
rows, err := q.db.Query(ctx, listAllUsuarios)
|
rows, err := q.db.Query(ctx, listAllUsuarios, dollar_1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -313,6 +379,73 @@ func (q *Queries) ListUsuariosPending(ctx context.Context, dollar_1 string) ([]L
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateCadastroCliente = `-- name: UpdateCadastroCliente :one
|
||||||
|
UPDATE cadastro_clientes
|
||||||
|
SET
|
||||||
|
nome = COALESCE($2, nome),
|
||||||
|
telefone = COALESCE($3, telefone),
|
||||||
|
cpf_cnpj = COALESCE($4, cpf_cnpj),
|
||||||
|
cep = COALESCE($5, cep),
|
||||||
|
endereco = COALESCE($6, endereco),
|
||||||
|
numero = COALESCE($7, numero),
|
||||||
|
complemento = COALESCE($8, complemento),
|
||||||
|
bairro = COALESCE($9, bairro),
|
||||||
|
cidade = COALESCE($10, cidade),
|
||||||
|
estado = COALESCE($11, estado),
|
||||||
|
atualizado_em = NOW()
|
||||||
|
WHERE usuario_id = $1
|
||||||
|
RETURNING id, usuario_id, empresa_id, nome, telefone, criado_em, atualizado_em, cpf_cnpj, cep, endereco, numero, complemento, bairro, cidade, estado
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateCadastroClienteParams struct {
|
||||||
|
UsuarioID pgtype.UUID `json:"usuario_id"`
|
||||||
|
Nome pgtype.Text `json:"nome"`
|
||||||
|
Telefone pgtype.Text `json:"telefone"`
|
||||||
|
CpfCnpj pgtype.Text `json:"cpf_cnpj"`
|
||||||
|
Cep pgtype.Text `json:"cep"`
|
||||||
|
Endereco pgtype.Text `json:"endereco"`
|
||||||
|
Numero pgtype.Text `json:"numero"`
|
||||||
|
Complemento pgtype.Text `json:"complemento"`
|
||||||
|
Bairro pgtype.Text `json:"bairro"`
|
||||||
|
Cidade pgtype.Text `json:"cidade"`
|
||||||
|
Estado pgtype.Text `json:"estado"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateCadastroCliente(ctx context.Context, arg UpdateCadastroClienteParams) (CadastroCliente, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateCadastroCliente,
|
||||||
|
arg.UsuarioID,
|
||||||
|
arg.Nome,
|
||||||
|
arg.Telefone,
|
||||||
|
arg.CpfCnpj,
|
||||||
|
arg.Cep,
|
||||||
|
arg.Endereco,
|
||||||
|
arg.Numero,
|
||||||
|
arg.Complemento,
|
||||||
|
arg.Bairro,
|
||||||
|
arg.Cidade,
|
||||||
|
arg.Estado,
|
||||||
|
)
|
||||||
|
var i CadastroCliente
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UsuarioID,
|
||||||
|
&i.EmpresaID,
|
||||||
|
&i.Nome,
|
||||||
|
&i.Telefone,
|
||||||
|
&i.CriadoEm,
|
||||||
|
&i.AtualizadoEm,
|
||||||
|
&i.CpfCnpj,
|
||||||
|
&i.Cep,
|
||||||
|
&i.Endereco,
|
||||||
|
&i.Numero,
|
||||||
|
&i.Complemento,
|
||||||
|
&i.Bairro,
|
||||||
|
&i.Cidade,
|
||||||
|
&i.Estado,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const updateUsuarioAtivo = `-- name: UpdateUsuarioAtivo :one
|
const updateUsuarioAtivo = `-- name: UpdateUsuarioAtivo :one
|
||||||
UPDATE usuarios
|
UPDATE usuarios
|
||||||
SET ativo = $2, atualizado_em = NOW()
|
SET ativo = $2, atualizado_em = NOW()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
ALTER TABLE cadastro_clientes
|
||||||
|
ADD COLUMN IF NOT EXISTS cpf_cnpj VARCHAR(20),
|
||||||
|
ADD COLUMN IF NOT EXISTS cep VARCHAR(10),
|
||||||
|
ADD COLUMN IF NOT EXISTS endereco VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS numero VARCHAR(20),
|
||||||
|
ADD COLUMN IF NOT EXISTS complemento VARCHAR(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS bairro VARCHAR(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS cidade VARCHAR(100),
|
||||||
|
ADD COLUMN IF NOT EXISTS estado CHAR(2);
|
||||||
|
|
@ -77,9 +77,44 @@ FROM usuarios u
|
||||||
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
LEFT JOIN cadastro_profissionais cp ON u.id = cp.usuario_id
|
||||||
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
LEFT JOIN cadastro_clientes cc ON u.id = cc.usuario_id
|
||||||
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
LEFT JOIN empresas e ON cc.empresa_id = e.id
|
||||||
|
WHERE $1::text = ANY(u.regioes_permitidas)
|
||||||
ORDER BY u.criado_em DESC;
|
ORDER BY u.criado_em DESC;
|
||||||
|
|
||||||
-- name: CreateCadastroCliente :one
|
-- name: CreateCadastroCliente :one
|
||||||
INSERT INTO cadastro_clientes (usuario_id, empresa_id, nome, telefone)
|
INSERT INTO cadastro_clientes (
|
||||||
VALUES ($1, $2, $3, $4)
|
usuario_id,
|
||||||
|
empresa_id,
|
||||||
|
nome,
|
||||||
|
telefone,
|
||||||
|
cpf_cnpj,
|
||||||
|
cep,
|
||||||
|
endereco,
|
||||||
|
numero,
|
||||||
|
complemento,
|
||||||
|
bairro,
|
||||||
|
cidade,
|
||||||
|
estado
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateCadastroCliente :one
|
||||||
|
UPDATE cadastro_clientes
|
||||||
|
SET
|
||||||
|
nome = COALESCE($2, nome),
|
||||||
|
telefone = COALESCE($3, telefone),
|
||||||
|
cpf_cnpj = COALESCE($4, cpf_cnpj),
|
||||||
|
cep = COALESCE($5, cep),
|
||||||
|
endereco = COALESCE($6, endereco),
|
||||||
|
numero = COALESCE($7, numero),
|
||||||
|
complemento = COALESCE($8, complemento),
|
||||||
|
bairro = COALESCE($9, bairro),
|
||||||
|
cidade = COALESCE($10, cidade),
|
||||||
|
estado = COALESCE($11, estado),
|
||||||
|
atualizado_em = NOW()
|
||||||
|
WHERE usuario_id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetCadastroClienteByUsuarioID :one
|
||||||
|
SELECT * FROM cadastro_clientes
|
||||||
|
WHERE usuario_id = $1 LIMIT 1;
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,16 @@ CREATE TABLE IF NOT EXISTS cadastro_clientes (
|
||||||
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
UNIQUE(usuario_id)
|
UNIQUE(usuario_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Migrations (Appended for Idempotency)
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS cpf_cnpj VARCHAR(20);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS cep VARCHAR(10);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS endereco VARCHAR(255);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS numero VARCHAR(20);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS complemento VARCHAR(100);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS bairro VARCHAR(100);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS cidade VARCHAR(100);
|
||||||
|
ALTER TABLE cadastro_clientes ADD COLUMN IF NOT EXISTS estado CHAR(2);
|
||||||
-- Agenda Table
|
-- Agenda Table
|
||||||
CREATE TABLE IF NOT EXISTS agenda (
|
CREATE TABLE IF NOT EXISTS agenda (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,23 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||||
id: backendUser.id,
|
id: backendUser.id,
|
||||||
email: backendUser.email,
|
email: backendUser.email,
|
||||||
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
||||||
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
||||||
allowedRegions: backendUser.allowed_regions || [], // Map allowed regions
|
allowedRegions: backendUser.allowed_regions || [], // Map allowed regions
|
||||||
|
|
||||||
|
// Client specific fields
|
||||||
|
cpf_cnpj: backendUser.cpf_cnpj,
|
||||||
|
cep: backendUser.cep,
|
||||||
|
endereco: backendUser.endereco,
|
||||||
|
numero: backendUser.numero,
|
||||||
|
complemento: backendUser.complemento,
|
||||||
|
bairro: backendUser.bairro,
|
||||||
|
cidade: backendUser.cidade,
|
||||||
|
estado: backendUser.estado,
|
||||||
};
|
};
|
||||||
console.log("AuthContext: restoreSession mapped user:", mappedUser);
|
console.log("AuthContext: restoreSession mapped user:", mappedUser);
|
||||||
if (!backendUser.ativo) {
|
if (!backendUser.ativo) {
|
||||||
|
|
@ -191,12 +202,23 @@ const login = async (email: string, password?: string) => {
|
||||||
id: backendUser.id,
|
id: backendUser.id,
|
||||||
email: backendUser.email,
|
email: backendUser.email,
|
||||||
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
name: data.profissional?.nome || data.empresa?.nome || backendUser.name || backendUser.nome || backendUser.email.split('@')[0],
|
||||||
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
avatar: data.profissional?.avatar_url || data.empresa?.avatar_url || backendUser.avatar,
|
||||||
allowedRegions: backendUser.allowed_regions || [],
|
allowedRegions: backendUser.allowed_regions || [],
|
||||||
|
|
||||||
|
// Client specific fields
|
||||||
|
cpf_cnpj: backendUser.cpf_cnpj,
|
||||||
|
cep: backendUser.cep,
|
||||||
|
endereco: backendUser.endereco,
|
||||||
|
numero: backendUser.numero,
|
||||||
|
complemento: backendUser.complemento,
|
||||||
|
bairro: backendUser.bairro,
|
||||||
|
cidade: backendUser.cidade,
|
||||||
|
estado: backendUser.estado,
|
||||||
};
|
};
|
||||||
|
|
||||||
setUser(mappedUser);
|
setUser(mappedUser);
|
||||||
|
|
@ -240,21 +262,38 @@ const login = async (email: string, password?: string) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async (data: { nome: string; email: string; senha: string; telefone: string; role: string; empresaId?: string; tipo_profissional?: string; regiao?: string }) => {
|
const register = async (data: any) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Destructure to separate empresaId from the rest
|
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8080";
|
||||||
const { empresaId, ...rest } = data;
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...rest,
|
email: data.email,
|
||||||
empresa_id: empresaId
|
senha: data.password || data.senha,
|
||||||
|
role: data.role,
|
||||||
|
nome: data.name || data.nome,
|
||||||
|
telefone: data.phone || data.telefone,
|
||||||
|
professional_type: data.professional_type || data.tipo_profissional,
|
||||||
|
empresa_id: data.company_id || data.empresaId || data.empresa_id,
|
||||||
|
regiao: data.regiao,
|
||||||
|
cpf_cnpj: data.cpf_cnpj,
|
||||||
|
cep: data.cep,
|
||||||
|
endereco: data.endereco,
|
||||||
|
numero: data.numero,
|
||||||
|
complemento: data.complemento,
|
||||||
|
bairro: data.bairro,
|
||||||
|
cidade: data.cidade,
|
||||||
|
estado: data.estado
|
||||||
};
|
};
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL}/auth/register`, {
|
|
||||||
method: 'POST',
|
const response = await fetch(`${API_URL}/auth/register`, {
|
||||||
headers: {
|
method: "POST",
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
'x-regiao': data.regiao || 'SP' // Pass region header
|
"Content-Type": "application/json",
|
||||||
|
"x-regiao": data.regiao || 'SP'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -265,9 +304,6 @@ const login = async (email: string, password?: string) => {
|
||||||
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|
||||||
// IF user is returned (auto-login), logic:
|
|
||||||
// Only set user/token if they are ACTIVE (which they won't be for standard clients/professionals)
|
|
||||||
// This allows the "Pending Approval" modal to show instead of auto-redirecting.
|
|
||||||
if (responseData.user && responseData.user.ativo) {
|
if (responseData.user && responseData.user.ativo) {
|
||||||
if (responseData.access_token) {
|
if (responseData.access_token) {
|
||||||
localStorage.setItem('token', responseData.access_token);
|
localStorage.setItem('token', responseData.access_token);
|
||||||
|
|
@ -279,14 +315,26 @@ const login = async (email: string, password?: string) => {
|
||||||
id: backendUser.id,
|
id: backendUser.id,
|
||||||
email: backendUser.email,
|
email: backendUser.email,
|
||||||
name: backendUser.nome || backendUser.email.split('@')[0],
|
name: backendUser.nome || backendUser.email.split('@')[0],
|
||||||
|
phone: backendUser.phone || backendUser.telefone || backendUser.whatsapp, // Map phone
|
||||||
role: backendUser.role as UserRole,
|
role: backendUser.role as UserRole,
|
||||||
ativo: backendUser.ativo,
|
ativo: backendUser.ativo,
|
||||||
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
empresaId: backendUser.company_id || backendUser.empresa_id || backendUser.companyId,
|
||||||
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
companyName: backendUser.company_name || backendUser.empresa_nome || backendUser.companyName,
|
||||||
|
avatar: backendUser.avatar,
|
||||||
|
allowedRegions: backendUser.allowed_regions || [],
|
||||||
|
|
||||||
|
// Client specific fields
|
||||||
|
cpf_cnpj: backendUser.cpf_cnpj,
|
||||||
|
cep: backendUser.cep,
|
||||||
|
endereco: backendUser.endereco,
|
||||||
|
numero: backendUser.numero,
|
||||||
|
complemento: backendUser.complemento,
|
||||||
|
bairro: backendUser.bairro,
|
||||||
|
cidade: backendUser.cidade,
|
||||||
|
estado: backendUser.estado,
|
||||||
};
|
};
|
||||||
setUser(mappedUser);
|
setUser(mappedUser);
|
||||||
}
|
}
|
||||||
// If user is NOT active, we do NOT set the token/user state, preventing auto-login.
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -295,7 +343,12 @@ const login = async (email: string, password?: string) => {
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Registration error:', err);
|
console.error('Registration error:', err);
|
||||||
throw err;
|
if (err instanceof Error) {
|
||||||
|
return { success: false, error: err.message };
|
||||||
|
}
|
||||||
|
return { success: false, error: "Erro desconhecido" };
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
ProfessionalData,
|
ProfessionalData,
|
||||||
} from "../components/ProfessionalForm";
|
} from "../components/ProfessionalForm";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
|
||||||
interface ProfessionalRegisterProps {
|
interface ProfessionalRegisterProps {
|
||||||
onNavigate: (page: string) => void;
|
onNavigate: (page: string) => void;
|
||||||
|
|
@ -59,7 +60,12 @@ export const ProfessionalRegister: React.FC<ProfessionalRegisterProps> = ({
|
||||||
console.log("Upload concluído. URL:", avatarUrl);
|
console.log("Upload concluído. URL:", avatarUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Erro no upload do avatar:", err);
|
console.error("Erro no upload do avatar:", err);
|
||||||
throw new Error("Falha ao enviar foto de perfil: " + (err instanceof Error ? err.message : "Erro desconhecido"));
|
// Não trava o cadastro, apenas avisa
|
||||||
|
toast.error("Não foi possível enviar a foto de perfil. O cadastro seguirá sem foto.", {
|
||||||
|
duration: 5000,
|
||||||
|
icon: '⚠️'
|
||||||
|
});
|
||||||
|
// Mantém avatarUrl vazio e prossegue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapear dados do formulário para o payload esperado pelo backend
|
// Mapear dados do formulário para o payload esperado pelo backend
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import { Navbar } from "../components/Navbar";
|
import { Navbar } from "../components/Navbar";
|
||||||
import { Button } from "../components/Button";
|
import { Button } from "../components/Button";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { getFunctions, createProfessional, updateProfessional } from "../services/apiService";
|
import { getFunctions, createProfessional, updateProfessional, updateUserProfile } from "../services/apiService";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { formatCPFCNPJ, formatPhone } from "../utils/masks";
|
import { formatCPFCNPJ, formatPhone } from "../utils/masks";
|
||||||
|
|
||||||
|
|
@ -127,6 +127,27 @@ export const ProfilePage: React.FC = () => {
|
||||||
const funcsRes = await getFunctions();
|
const funcsRes = await getFunctions();
|
||||||
if (funcsRes.data) setFunctions(funcsRes.data);
|
if (funcsRes.data) setFunctions(funcsRes.data);
|
||||||
|
|
||||||
|
if (user?.role === "EVENT_OWNER") {
|
||||||
|
// Clients don't have professional profile. Populate from User.
|
||||||
|
// Ensure user object has these fields (mapped in AuthContext)
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
nome: user.name || "",
|
||||||
|
email: user.email || "",
|
||||||
|
whatsapp: user.phone || "",
|
||||||
|
cpf_cnpj_titular: user.cpf_cnpj || "",
|
||||||
|
cep: user.cep || "",
|
||||||
|
endereco: user.endereco || "",
|
||||||
|
numero: user.numero || "",
|
||||||
|
complemento: user.complemento || "",
|
||||||
|
bairro: user.bairro || "",
|
||||||
|
cidade: user.cidade || "",
|
||||||
|
uf: user.estado || "",
|
||||||
|
});
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to fetch existing profile
|
// Try to fetch existing profile
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL || "http://localhost:8080"}/api/profissionais/me`, {
|
const response = await fetch(`${import.meta.env.VITE_API_URL || "http://localhost:8080"}/api/profissionais/me`, {
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
|
@ -270,6 +291,26 @@ export const ProfilePage: React.FC = () => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
if (!token) throw new Error("Usuário não autenticado");
|
if (!token) throw new Error("Usuário não autenticado");
|
||||||
|
|
||||||
|
if (user?.role === "EVENT_OWNER") {
|
||||||
|
const clientPayload = {
|
||||||
|
name: formData.nome,
|
||||||
|
phone: formData.whatsapp,
|
||||||
|
cpf_cnpj: formData.cpf_cnpj_titular,
|
||||||
|
cep: formData.cep,
|
||||||
|
endereco: formData.endereco,
|
||||||
|
numero: formData.numero,
|
||||||
|
complemento: formData.complemento,
|
||||||
|
bairro: formData.bairro,
|
||||||
|
cidade: formData.cidade,
|
||||||
|
estado: formData.uf
|
||||||
|
};
|
||||||
|
const res = await updateUserProfile(clientPayload, token);
|
||||||
|
if (res.error) throw new Error(res.error);
|
||||||
|
toast.success("Perfil atualizado com sucesso!");
|
||||||
|
setIsSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Payload preparation
|
// Payload preparation
|
||||||
// For create/update, we need `funcao_profissional_id` (single) for backward compatibility optionally
|
// For create/update, we need `funcao_profissional_id` (single) for backward compatibility optionally
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Button } from "../components/Button";
|
||||||
import { Input } from "../components/Input";
|
import { Input } from "../components/Input";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { getCompanies } from "../services/apiService";
|
import { getCompanies } from "../services/apiService";
|
||||||
|
import { formatPhone, formatCPFCNPJ, formatCEP } from "../utils/masks";
|
||||||
|
|
||||||
interface RegisterProps {
|
interface RegisterProps {
|
||||||
onNavigate: (page: string) => void;
|
onNavigate: (page: string) => void;
|
||||||
|
|
@ -21,6 +22,14 @@ export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
empresaId: "",
|
empresaId: "",
|
||||||
|
cpfCnpj: "",
|
||||||
|
cep: "",
|
||||||
|
endereco: "",
|
||||||
|
numero: "",
|
||||||
|
complemento: "",
|
||||||
|
bairro: "",
|
||||||
|
cidade: "",
|
||||||
|
estado: "",
|
||||||
});
|
});
|
||||||
const [agreedToTerms, setAgreedToTerms] = useState(false);
|
const [agreedToTerms, setAgreedToTerms] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
@ -84,12 +93,20 @@ export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await register({
|
await register({
|
||||||
nome: formData.name,
|
name: formData.name,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
senha: formData.password,
|
password: formData.password,
|
||||||
telefone: formData.phone,
|
phone: formData.phone,
|
||||||
role: "EVENT_OWNER", // Client Role
|
role: "EVENT_OWNER", // Client Role
|
||||||
empresaId: formData.empresaId,
|
company_id: formData.empresaId,
|
||||||
|
cpf_cnpj: formData.cpfCnpj,
|
||||||
|
cep: formData.cep,
|
||||||
|
endereco: formData.endereco,
|
||||||
|
numero: formData.numero,
|
||||||
|
complemento: formData.complemento,
|
||||||
|
bairro: formData.bairro,
|
||||||
|
cidade: formData.cidade,
|
||||||
|
estado: formData.estado,
|
||||||
});
|
});
|
||||||
// Limpar dados de sessão após cadastro bem-sucedido
|
// Limpar dados de sessão após cadastro bem-sucedido
|
||||||
sessionStorage.removeItem('accessCodeValidated');
|
sessionStorage.removeItem('accessCodeValidated');
|
||||||
|
|
@ -233,9 +250,82 @@ export const Register: React.FC<RegisterProps> = ({ onNavigate }) => {
|
||||||
required
|
required
|
||||||
placeholder="(00) 00000-0000"
|
placeholder="(00) 00000-0000"
|
||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={(e) => handleChange("phone", e.target.value)}
|
onChange={(e) => handleChange("phone", formatPhone(e.target.value))}
|
||||||
mask="phone"
|
mask="phone"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="CPF/CNPJ"
|
||||||
|
value={formData.cpfCnpj}
|
||||||
|
onChange={(e) => handleChange("cpfCnpj", formatCPFCNPJ(e.target.value))}
|
||||||
|
placeholder="000.000.000-00"
|
||||||
|
maxLength={18}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<Input
|
||||||
|
label="CEP"
|
||||||
|
value={formData.cep}
|
||||||
|
onChange={(e) => handleChange("cep", formatCEP(e.target.value))}
|
||||||
|
placeholder="00000-000"
|
||||||
|
onBlur={(e) => {
|
||||||
|
const cep = e.target.value.replace(/\D/g, '');
|
||||||
|
if (cep.length === 8) {
|
||||||
|
fetch(`https://viacep.com.br/ws/${cep}/json/`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.erro) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
endereco: data.logradouro,
|
||||||
|
bairro: data.bairro,
|
||||||
|
cidade: data.localidade,
|
||||||
|
estado: data.uf
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Número"
|
||||||
|
value={formData.numero}
|
||||||
|
onChange={(e) => handleChange("numero", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
label="Endereço"
|
||||||
|
value={formData.endereco}
|
||||||
|
onChange={(e) => handleChange("endereco", e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<Input
|
||||||
|
label="Complemento"
|
||||||
|
value={formData.complemento}
|
||||||
|
onChange={(e) => handleChange("complemento", e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Bairro"
|
||||||
|
value={formData.bairro}
|
||||||
|
onChange={(e) => handleChange("bairro", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-[2fr_1fr] gap-3">
|
||||||
|
<Input
|
||||||
|
label="Cidade"
|
||||||
|
value={formData.cidade}
|
||||||
|
onChange={(e) => handleChange("cidade", e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="UF"
|
||||||
|
value={formData.estado}
|
||||||
|
onChange={(e) => handleChange("estado", e.target.value)}
|
||||||
|
maxLength={2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] sm:text-xs md:text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-[10px] sm:text-xs md:text-sm font-medium text-gray-700 mb-1">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
approveUser as apiApproveUser,
|
approveUser as apiApproveUser,
|
||||||
rejectUser as apiRejectUser,
|
rejectUser as apiRejectUser,
|
||||||
updateUserRole,
|
updateUserRole,
|
||||||
|
getCompanies,
|
||||||
} from "../services/apiService";
|
} from "../services/apiService";
|
||||||
import { UserApprovalStatus, UserRole } from "../types";
|
import { UserApprovalStatus, UserRole } from "../types";
|
||||||
import {
|
import {
|
||||||
|
|
@ -21,6 +22,7 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "../components/Button";
|
import { Button } from "../components/Button";
|
||||||
import { Input } from "../components/Input";
|
import { Input } from "../components/Input";
|
||||||
|
import { formatPhone, formatCPFCNPJ, formatCEP } from "../utils/masks";
|
||||||
|
|
||||||
// INTERFACES
|
// INTERFACES
|
||||||
interface UserApprovalProps {
|
interface UserApprovalProps {
|
||||||
|
|
@ -194,6 +196,16 @@ const CreateUserModal: React.FC<CreateUserModalProps> = ({
|
||||||
formData,
|
formData,
|
||||||
setFormData
|
setFormData
|
||||||
}) => {
|
}) => {
|
||||||
|
// Fetch companies
|
||||||
|
const [companies, setCompanies] = useState<any[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (formData.role === "EVENT_OWNER") {
|
||||||
|
getCompanies().then(res => {
|
||||||
|
if(res.data) setCompanies(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [formData.role]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4 fade-in">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4 fade-in">
|
||||||
|
|
@ -226,7 +238,7 @@ const CreateUserModal: React.FC<CreateUserModalProps> = ({
|
||||||
<Input
|
<Input
|
||||||
label="Telefone (Whatsapp)"
|
label="Telefone (Whatsapp)"
|
||||||
value={formData.telefone}
|
value={formData.telefone}
|
||||||
onChange={(e) => setFormData({...formData, telefone: e.target.value})}
|
onChange={(e) => setFormData({...formData, telefone: formatPhone(e.target.value)})}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -237,39 +249,135 @@ const CreateUserModal: React.FC<CreateUserModalProps> = ({
|
||||||
value={formData.senha}
|
value={formData.senha}
|
||||||
onChange={(e) => setFormData({...formData, senha: e.target.value})}
|
onChange={(e) => setFormData({...formData, senha: e.target.value})}
|
||||||
/>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<div>
|
||||||
Função
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
</label>
|
Tipo de Usuário
|
||||||
<select
|
</label>
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#B9CF33] focus:border-transparent"
|
<select
|
||||||
value={formData.role}
|
value={formData.role}
|
||||||
onChange={(e) => setFormData({...formData, role: e.target.value as any})}
|
onChange={(e) => setFormData({...formData, role: e.target.value})}
|
||||||
>
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-photum-green focus:border-transparent outline-none transition-all"
|
||||||
<option value="PHOTOGRAPHER">Profissional</option>
|
>
|
||||||
<option value="RESEARCHER">Pesquisador</option>
|
<option value="PHOTOGRAPHER">Fotógrafo</option>
|
||||||
<option value="BUSINESS_OWNER">Dono do Negócio</option>
|
<option value="EVENT_OWNER">Cliente (Empresa)</option>
|
||||||
<option value="SUPERADMIN">Super Admin</option>
|
<option value="BUSINESS_OWNER">Dono de Negócio</option>
|
||||||
<option value="EVENT_OWNER">Cliente (Empresa)</option>
|
<option value="ADMIN">Administrador</option>
|
||||||
</select>
|
<option value="AGENDA_VIEWER">Visualizador de Agenda</option>
|
||||||
</div>
|
<option value="RESEARCHER">Pesquisador</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formData.role === "EVENT_OWNER" && (
|
||||||
|
<div className="space-y-4 border-t pt-4 mt-2">
|
||||||
|
<h4 className="font-medium text-gray-900">Dados do Cliente</h4>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<Input
|
||||||
|
label="CPF/CNPJ"
|
||||||
|
value={formData.cpfCnpj || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, cpfCnpj: formatCPFCNPJ(e.target.value)})}
|
||||||
|
placeholder="000.000.000-00"
|
||||||
|
maxLength={18}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="CEP"
|
||||||
|
value={formData.cep || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, cep: formatCEP(e.target.value)})}
|
||||||
|
placeholder="00000-000"
|
||||||
|
onBlur={(e) => {
|
||||||
|
const cep = e.target.value.replace(/\D/g, '');
|
||||||
|
if (cep.length === 8) {
|
||||||
|
fetch(`https://viacep.com.br/ws/${cep}/json/`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.erro) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
endereco: data.logradouro,
|
||||||
|
bairro: data.bairro,
|
||||||
|
cidade: data.localidade,
|
||||||
|
estado: data.uf
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-[2fr_1fr] gap-4">
|
||||||
|
<Input
|
||||||
|
label="Endereço"
|
||||||
|
value={formData.endereco || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, endereco: e.target.value})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Número"
|
||||||
|
value={formData.numero || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, numero: e.target.value})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<Input
|
||||||
|
label="Complemento"
|
||||||
|
value={formData.complemento || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, complemento: e.target.value})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Bairro"
|
||||||
|
value={formData.bairro || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, bairro: e.target.value})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-[2fr_1fr] gap-4">
|
||||||
|
<Input
|
||||||
|
label="Cidade"
|
||||||
|
value={formData.cidade || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, cidade: e.target.value})}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="UF"
|
||||||
|
value={formData.estado || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, estado: e.target.value})}
|
||||||
|
maxLength={2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Empresa *
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
required
|
||||||
|
value={formData.empresa_id || ''}
|
||||||
|
onChange={(e) => setFormData({...formData, empresa_id: e.target.value})}
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-photum-green focus:border-transparent outline-none transition-all"
|
||||||
|
>
|
||||||
|
<option value="">Selecione uma empresa</option>
|
||||||
|
{companies.map(c => (
|
||||||
|
<option key={c.id} value={c.id}>{c.nome}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{formData.role === "PHOTOGRAPHER" && (
|
{formData.role === "PHOTOGRAPHER" && (
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<div>
|
||||||
Tipo de Profissional
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
</label>
|
Tipo de Profissional
|
||||||
<select
|
</label>
|
||||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#B9CF33] focus:border-transparent"
|
<select
|
||||||
value={formData.professional_type}
|
value={formData.professional_type}
|
||||||
onChange={(e) => setFormData({...formData, professional_type: e.target.value})}
|
onChange={(e) => setFormData({...formData, professional_type: e.target.value})}
|
||||||
>
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-photum-green focus:border-transparent outline-none transition-all"
|
||||||
<option value="">Selecione...</option>
|
>
|
||||||
<option value="Fotógrafo">Fotógrafo</option>
|
<option value="">Selecione...</option>
|
||||||
<option value="Cinegrafista">Cinegrafista</option>
|
<option value="FOTOGRAFO">Fotógrafo</option>
|
||||||
<option value="Recepcionista">Recepcionista</option>
|
<option value="CINEGRAFISTA">Cinegrafista</option>
|
||||||
</select>
|
<option value="EDITOR">Editor</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -316,6 +424,16 @@ export const UserApproval: React.FC<UserApprovalProps> = ({ onNavigate }) => {
|
||||||
role: "PHOTOGRAPHER",
|
role: "PHOTOGRAPHER",
|
||||||
telefone: "",
|
telefone: "",
|
||||||
professional_type: "", // For photographer subtype
|
professional_type: "", // For photographer subtype
|
||||||
|
empresa_id: "",
|
||||||
|
cpfCnpj: "",
|
||||||
|
cep: "",
|
||||||
|
endereco: "",
|
||||||
|
numero: "",
|
||||||
|
complemento: "",
|
||||||
|
bairro: "",
|
||||||
|
cidade: "",
|
||||||
|
estado: "",
|
||||||
|
regiao: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
|
|
@ -393,7 +511,17 @@ export const UserApproval: React.FC<UserApprovalProps> = ({ onNavigate }) => {
|
||||||
senha: createFormData.senha,
|
senha: createFormData.senha,
|
||||||
role: createFormData.role,
|
role: createFormData.role,
|
||||||
telefone: createFormData.telefone,
|
telefone: createFormData.telefone,
|
||||||
tipo_profissional: createFormData.role === "PHOTOGRAPHER" ? createFormData.professional_type : undefined
|
professional_type: createFormData.role === "PHOTOGRAPHER" ? createFormData.professional_type : undefined,
|
||||||
|
empresa_id: createFormData.role === "EVENT_OWNER" ? createFormData.empresa_id : undefined,
|
||||||
|
cpf_cnpj: createFormData.role === "EVENT_OWNER" ? createFormData.cpfCnpj : undefined,
|
||||||
|
cep: createFormData.role === "EVENT_OWNER" ? createFormData.cep : undefined,
|
||||||
|
endereco: createFormData.role === "EVENT_OWNER" ? createFormData.endereco : undefined,
|
||||||
|
numero: createFormData.role === "EVENT_OWNER" ? createFormData.numero : undefined,
|
||||||
|
complemento: createFormData.role === "EVENT_OWNER" ? createFormData.complemento : undefined,
|
||||||
|
bairro: createFormData.role === "EVENT_OWNER" ? createFormData.bairro : undefined,
|
||||||
|
cidade: createFormData.role === "EVENT_OWNER" ? createFormData.cidade : undefined,
|
||||||
|
estado: createFormData.role === "EVENT_OWNER" ? createFormData.estado : undefined,
|
||||||
|
regiao: createFormData.regiao || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await createAdminUser(payload, token);
|
const result = await createAdminUser(payload, token);
|
||||||
|
|
@ -403,7 +531,8 @@ export const UserApproval: React.FC<UserApprovalProps> = ({ onNavigate }) => {
|
||||||
alert("Usuário criado com sucesso!");
|
alert("Usuário criado com sucesso!");
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
setCreateFormData({
|
setCreateFormData({
|
||||||
nome: "", email: "", senha: "", role: "PHOTOGRAPHER", telefone: "", professional_type: ""
|
nome: "", email: "", senha: "", role: "PHOTOGRAPHER", telefone: "", professional_type: "", empresa_id: "",
|
||||||
|
cpfCnpj: "", cep: "", endereco: "", numero: "", complemento: "", bairro: "", cidade: "", estado: "", regiao: ""
|
||||||
});
|
});
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,42 @@ export async function updateProfessional(id: string, data: any, token: string):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o perfil do usuário logado (Cliente/Evento Owner)
|
||||||
|
*/
|
||||||
|
export async function updateUserProfile(data: any, token: string): Promise<ApiResponse<any>> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/me`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${token}`,
|
||||||
|
"x-regiao": localStorage.getItem("photum_selected_region") || "SP"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await response.json();
|
||||||
|
return {
|
||||||
|
data: responseData,
|
||||||
|
error: null,
|
||||||
|
isBackendDown: false,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating user profile:", error);
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
error: error instanceof Error ? error.message : "Erro desconhecido",
|
||||||
|
isBackendDown: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove um profissional
|
* Remove um profissional
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,18 @@ export interface User {
|
||||||
ativo?: boolean;
|
ativo?: boolean;
|
||||||
empresaId?: string; // ID da empresa vinculada (para Business Owners)
|
empresaId?: string; // ID da empresa vinculada (para Business Owners)
|
||||||
companyName?: string; // Nome da empresa vinculada
|
companyName?: string; // Nome da empresa vinculada
|
||||||
allowedRegions?: string[]; // Regiões permitidas
|
allowedRegions?: string[]; // Regiões permitidas para o usuário
|
||||||
|
|
||||||
|
// Client / Event Owner specific fields
|
||||||
|
cpf_cnpj?: string;
|
||||||
|
cep?: string;
|
||||||
|
endereco?: string;
|
||||||
|
numero?: string;
|
||||||
|
complemento?: string;
|
||||||
|
bairro?: string;
|
||||||
|
cidade?: string;
|
||||||
|
estado?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Institution {
|
export interface Institution {
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,26 @@
|
||||||
export const formatCPFCNPJ = (value: string) => {
|
export const formatCPFCNPJ = (value: string) => {
|
||||||
const clean = value.replace(/\D/g, "");
|
const digits = value.replace(/\D/g, "");
|
||||||
|
if (digits.length <= 11) {
|
||||||
if (clean.length <= 11) {
|
return digits
|
||||||
// CPF
|
|
||||||
return clean
|
|
||||||
.replace(/(\d{3})(\d)/, "$1.$2")
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||||
.replace(/(\d{3})(\d)/, "$1.$2")
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||||
.replace(/(\d{3})(\d{1,2})/, "$1-$2")
|
.replace(/(\d{3})(\d{1,2})$/, "$1-$2");
|
||||||
.replace(/(-\d{2})\d+?$/, "$1");
|
|
||||||
} else {
|
} else {
|
||||||
// CNPJ
|
return digits
|
||||||
return clean
|
|
||||||
.replace(/^(\d{2})(\d)/, "$1.$2")
|
.replace(/^(\d{2})(\d)/, "$1.$2")
|
||||||
.replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3")
|
.replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3")
|
||||||
.replace(/\.(\d{3})(\d)/, ".$1/$2")
|
.replace(/\.(\d{3})(\d)/, ".$1/$2")
|
||||||
.replace(/(\d{4})(\d)/, "$1-$2")
|
.replace(/(\d{4})(\d)/, "$1-$2");
|
||||||
.replace(/(-\d{2})\d+?$/, "$1");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const formatCEP = (value: string) => {
|
||||||
|
return value
|
||||||
|
.replace(/\D/g, "")
|
||||||
|
.replace(/^(\d{5})(\d)/, "$1-$2")
|
||||||
|
.slice(0, 9);
|
||||||
|
};
|
||||||
|
|
||||||
export const formatPhone = (value: string) => {
|
export const formatPhone = (value: string) => {
|
||||||
const clean = value.replace(/\D/g, "");
|
const clean = value.replace(/\D/g, "");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue