chore: fix prices
This commit is contained in:
@@ -10,9 +10,9 @@ import (
|
||||
|
||||
// AddProductInBasket ajoute un produit au panier de l'utilisateur
|
||||
func (d *Database) AddProductInBasket(username, nameProduct string, quantity float64, category string) (*models.Panier, error) {
|
||||
// Rechercher le produit par son nom et catégorie
|
||||
// Rechercher le produit par son nom et catégorie (case-insensitive)
|
||||
var productID int
|
||||
productQuery := `SELECT id FROM products WHERE name = $1 AND category = $2`
|
||||
productQuery := `SELECT id FROM products WHERE LOWER(name) = LOWER($1) AND LOWER(category) = LOWER($2)`
|
||||
err := d.QueryRow(productQuery, nameProduct, category).Scan(&productID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("produit '%s' non trouvé dans la catégorie '%s'", nameProduct, category)
|
||||
@@ -29,18 +29,19 @@ func (d *Database) AddProductInBasket(username, nameProduct string, quantity flo
|
||||
|
||||
// Vérifier si le produit existe déjà dans le panier
|
||||
var existingID int
|
||||
var existingQuantity float64
|
||||
checkQuery := `SELECT id, quantity FROM baskets WHERE username = $1 AND product_id = $2`
|
||||
err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity)
|
||||
var existingQuantity, existingPrice float64
|
||||
checkQuery := `SELECT id, quantity, price FROM baskets WHERE username = $1 AND product_id = $2`
|
||||
err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity, &existingPrice)
|
||||
|
||||
if err == nil {
|
||||
// Produit déjà dans le panier : mettre à jour quantité et prix
|
||||
// Produit déjà dans le panier : cumuler quantité et prix total de la ligne
|
||||
newQuantity := existingQuantity + quantity
|
||||
newPrice := existingPrice + price
|
||||
updateQuery := `UPDATE baskets SET quantity = $1, price = $2, created_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3 RETURNING id, username, product_id, quantity, price, created_at`
|
||||
|
||||
var basket models.Panier
|
||||
err = d.QueryRow(updateQuery, newQuantity, price, existingID).Scan(
|
||||
err = d.QueryRow(updateQuery, newQuantity, newPrice, existingID).Scan(
|
||||
&basket.ID,
|
||||
&basket.Username,
|
||||
&basket.ProductID,
|
||||
@@ -75,7 +76,85 @@ func (d *Database) AddProductInBasket(username, nameProduct string, quantity flo
|
||||
return &basket, nil
|
||||
}
|
||||
|
||||
// GetProductPrice récupère le prix réel d'un produit pour une quantité donnée
|
||||
// GetProductPriceByID récupère le prix d'un produit par son ID et quantité (NUMERIC exact)
|
||||
func (d *Database) GetProductPriceByID(productID int, quantity float64) (float64, error) {
|
||||
var price float64
|
||||
|
||||
// Comparaison NUMERIC précise : évite les problèmes float64 vs NUMERIC(10,3)
|
||||
exactQuery := `
|
||||
SELECT price FROM product_prices
|
||||
WHERE product_id = $1 AND quantity = ROUND($2::NUMERIC, 3)
|
||||
LIMIT 1
|
||||
`
|
||||
err := d.QueryRow(exactQuery, productID, quantity).Scan(&price)
|
||||
if err == nil {
|
||||
return price, nil
|
||||
}
|
||||
|
||||
// Fallback : palier inférieur le plus proche
|
||||
tierQuery := `
|
||||
SELECT price FROM product_prices
|
||||
WHERE product_id = $1 AND quantity <= ROUND($2::NUMERIC, 3)
|
||||
ORDER BY quantity DESC LIMIT 1
|
||||
`
|
||||
err = d.QueryRow(tierQuery, productID, quantity).Scan(&price)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("aucun prix trouvé pour product_id=%d qty=%.3f: %w", productID, quantity, err)
|
||||
}
|
||||
return price, nil
|
||||
}
|
||||
|
||||
// GetProductStockByID récupère le stock d'un produit par son ID
|
||||
func (d *Database) GetProductStockByID(productID int) (float64, error) {
|
||||
var stock float64
|
||||
err := d.QueryRow(`SELECT stock FROM products WHERE id = $1`, productID).Scan(&stock)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("produit %d non trouvé: %w", productID, err)
|
||||
}
|
||||
return stock, nil
|
||||
}
|
||||
|
||||
// AddProductInBasketByID ajoute un produit au panier en utilisant son ID directement
|
||||
func (d *Database) AddProductInBasketByID(username string, productID int, quantity float64) (*models.Panier, error) {
|
||||
price, err := d.GetProductPriceByID(productID, quantity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erreur récupération prix: %w", err)
|
||||
}
|
||||
|
||||
var existingID int
|
||||
var existingQuantity, existingPrice float64
|
||||
checkQuery := `SELECT id, quantity, price FROM baskets WHERE username = $1 AND product_id = $2`
|
||||
err = d.QueryRow(checkQuery, username, productID).Scan(&existingID, &existingQuantity, &existingPrice)
|
||||
|
||||
if err == nil {
|
||||
newQuantity := existingQuantity + quantity
|
||||
newPrice := existingPrice + price
|
||||
updateQuery := `UPDATE baskets SET quantity = $1, price = $2, created_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3 RETURNING id, username, product_id, quantity, price, created_at`
|
||||
var basket models.Panier
|
||||
err = d.QueryRow(updateQuery, newQuantity, newPrice, existingID).Scan(
|
||||
&basket.ID, &basket.Username, &basket.ProductID, &basket.Quantity, &basket.Price, &basket.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erreur lors de la mise à jour du panier: %w", err)
|
||||
}
|
||||
return &basket, nil
|
||||
}
|
||||
|
||||
insertQuery := `INSERT INTO baskets (username, product_id, quantity, price, created_at)
|
||||
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
|
||||
RETURNING id, username, product_id, quantity, price, created_at`
|
||||
var basket models.Panier
|
||||
err = d.QueryRow(insertQuery, username, productID, quantity, price).Scan(
|
||||
&basket.ID, &basket.Username, &basket.ProductID, &basket.Quantity, &basket.Price, &basket.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("erreur lors de l'ajout au panier: %w", err)
|
||||
}
|
||||
return &basket, nil
|
||||
}
|
||||
|
||||
// GetProductPrice récupère le prix réel d'un produit pour une quantité donnée (legacy)
|
||||
func (d *Database) GetProductPrice(name, category string, quantity float64) (float64, error) {
|
||||
var price float64
|
||||
|
||||
@@ -100,7 +179,7 @@ func (d *Database) GetProductPrice(name, category string, quantity float64) (flo
|
||||
|
||||
func (d *Database) GetProductStock(name, category string) (float64, error) {
|
||||
var stock float64
|
||||
query := `SELECT stock FROM products WHERE name = $1 AND category = $2`
|
||||
query := `SELECT stock FROM products WHERE LOWER(name) = LOWER($1) AND LOWER(category) = LOWER($2)`
|
||||
err := d.QueryRow(query, name, category).Scan(&stock)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("produit non trouvé: %w", err)
|
||||
@@ -109,7 +188,7 @@ func (d *Database) GetProductStock(name, category string) (float64, error) {
|
||||
}
|
||||
|
||||
func (d *Database) DecrementProductStock(name, category string, quantity float64) error {
|
||||
query := `UPDATE products SET stock = stock - $1 WHERE name = $2 AND category = $3 AND stock >= $1`
|
||||
query := `UPDATE products SET stock = stock - $1 WHERE LOWER(name) = LOWER($2) AND LOWER(category) = LOWER($3) AND stock >= $1`
|
||||
result, err := d.Exec(query, quantity, name, category)
|
||||
if err != nil {
|
||||
return fmt.Errorf("erreur mise à jour stock: %w", err)
|
||||
@@ -223,8 +302,9 @@ func (d *Database) ClearBasket(username string) error {
|
||||
}
|
||||
|
||||
// GetBasketTotal calcule le montant total du panier d'un utilisateur
|
||||
// price dans baskets = prix total de la ligne (cumul des ajouts)
|
||||
func (d *Database) GetBasketTotal(username string) (float64, error) {
|
||||
query := `SELECT COALESCE(SUM(quantity * price), 0) as total
|
||||
query := `SELECT COALESCE(SUM(price), 0) as total
|
||||
FROM baskets
|
||||
WHERE username = $1`
|
||||
|
||||
|
||||
@@ -990,3 +990,22 @@ func (d *Database) CanUserAccessCommand(
|
||||
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// SaveClientPushToken enregistre le push token Expo d'un client
|
||||
func (d *Database) SaveClientPushToken(clientID int, pushToken string) error {
|
||||
_, err := d.Exec(`UPDATE clients SET push_token = $1 WHERE id = $2`, pushToken, clientID)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteClientPushToken supprime le push token d'un client
|
||||
func (d *Database) DeleteClientPushToken(clientID int) error {
|
||||
_, err := d.Exec(`UPDATE clients SET push_token = NULL WHERE id = $1`, clientID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetClientPushToken retourne le push token d'un client par son username
|
||||
func (d *Database) GetClientPushToken(username string) (string, error) {
|
||||
var token string
|
||||
err := d.QueryRow(`SELECT COALESCE(push_token, '') FROM clients WHERE username = $1`, username).Scan(&token)
|
||||
return token, err
|
||||
}
|
||||
|
||||
@@ -71,11 +71,16 @@ func InitDB() *Database {
|
||||
|
||||
log.Println("✅ Tables créées avec succès")
|
||||
|
||||
// Migration: ajouter colonne must_change_password si elle n'existe pas
|
||||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT TRUE`); err != nil {
|
||||
// Migration: ajouter colonne must_change_password si elle n'existe pas (DEFAULT FALSE pour les clients existants)
|
||||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT FALSE`); err != nil {
|
||||
log.Fatalf("❌ Erreur migration must_change_password: %v", err)
|
||||
}
|
||||
|
||||
// Migration: ajouter colonne push_token pour les notifications push
|
||||
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS push_token TEXT`); err != nil {
|
||||
log.Fatalf("❌ Erreur migration push_token: %v", err)
|
||||
}
|
||||
|
||||
// Lancer le nettoyage périodique des tokens expirés
|
||||
go database.cleanExpiredTokensPeriodically()
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -21,12 +23,57 @@ func (d *Database) NotifyClient(username string, commandID int, notifType, messa
|
||||
|
||||
notifJSON, _ := json.Marshal(notification)
|
||||
Redis.LPush(RedisCtx, notifKey, notifJSON)
|
||||
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour) // Expire après 7 jours
|
||||
Redis.Expire(RedisCtx, notifKey, 7*24*time.Hour)
|
||||
|
||||
// Envoyer push notification si le client a un token enregistré
|
||||
pushToken, err := d.GetClientPushToken(username)
|
||||
if err == nil && pushToken != "" {
|
||||
go sendExpoPush(pushToken, "Uber Stup", message, commandID, notifType)
|
||||
}
|
||||
|
||||
log.Printf("📬 Notification envoyée à %s: %s", username, message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendExpoPush(token, title, body string, commandID int, notifType string) {
|
||||
payload := map[string]interface{}{
|
||||
"to": token,
|
||||
"title": title,
|
||||
"body": body,
|
||||
"channelId": "orders",
|
||||
"data": map[string]interface{}{
|
||||
"command_id": commandID,
|
||||
"type": notifType,
|
||||
},
|
||||
"sound": "default",
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
log.Printf("❌ [EXPO_PUSH] Erreur marshal: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://exp.host/--/api/v2/push/send", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
log.Printf("❌ [EXPO_PUSH] Erreur création requête: %v", err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Accept-Encoding", "gzip, deflate")
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("❌ [EXPO_PUSH] Erreur envoi: %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
log.Printf("✅ [EXPO_PUSH] Push envoyé à %s (status: %d)", token, resp.StatusCode)
|
||||
}
|
||||
|
||||
// AddDeliveryRating ajoute une note pour un livreur
|
||||
func (d *Database) AddDeliveryRating(livreurUsername string, commandID, rating int, comment string) error {
|
||||
query := `
|
||||
|
||||
@@ -9,6 +9,54 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterPushToken enregistre le push token Expo d'un client
|
||||
// POST /api/v1/push-token
|
||||
func RegisterPushToken(c *gin.Context) {
|
||||
clientID := c.GetInt("client_id")
|
||||
if clientID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
PushToken string `json:"push_token" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "push_token requis"})
|
||||
return
|
||||
}
|
||||
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
if err := database.SaveClientPushToken(clientID, req.PushToken); err != nil {
|
||||
log.Printf("❌ [PUSH_TOKEN] Erreur sauvegarde token pour client %d: %v", clientID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur enregistrement push token"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [PUSH_TOKEN] Token enregistré pour client %d", clientID)
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
// UnregisterPushToken supprime le push token d'un client (au logout)
|
||||
// DELETE /api/v1/push-token
|
||||
func UnregisterPushToken(c *gin.Context) {
|
||||
clientID := c.GetInt("client_id")
|
||||
if clientID == 0 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Non authentifié"})
|
||||
return
|
||||
}
|
||||
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
if err := database.DeleteClientPushToken(clientID); err != nil {
|
||||
log.Printf("❌ [PUSH_TOKEN] Erreur suppression token pour client %d: %v", clientID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur suppression push token"})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [PUSH_TOKEN] Token supprimé pour client %d", clientID)
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
// GetClientNotifications retourne les notifications du client connecté
|
||||
// GET /api/v1/notifications
|
||||
func GetClientNotifications(c *gin.Context) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
type BasketsRequest struct {
|
||||
Username string `json:"username"`
|
||||
ProductID int `json:"product_id"`
|
||||
NameProduct string `json:"name_product"`
|
||||
Category string `json:"category"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
@@ -26,16 +27,14 @@ type BasketsRequest struct {
|
||||
// ============================================
|
||||
// POST /api/v1/panier/add
|
||||
func AddProductsBasket(c *gin.Context) {
|
||||
db := c.MustGet("database").(*db.Database)
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// Liaison JSON
|
||||
var req BasketsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Requête invalide", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Récupérer le username depuis JWT ou contexte
|
||||
username, ok := c.Get("username")
|
||||
if !ok {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentification requise"})
|
||||
@@ -43,45 +42,65 @@ func AddProductsBasket(c *gin.Context) {
|
||||
}
|
||||
req.Username = username.(string)
|
||||
|
||||
// Validation des champs
|
||||
if req.NameProduct == "" || req.Category == "" || req.Quantity <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Champs invalides"})
|
||||
if req.Quantity <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Quantité invalide"})
|
||||
return
|
||||
}
|
||||
|
||||
// Vérifier le stock
|
||||
stock, err := db.GetProductStock(req.NameProduct, req.Category)
|
||||
// Si product_id fourni par le mobile, on l'utilise directement (plus fiable)
|
||||
if req.ProductID > 0 {
|
||||
stock, err := database.GetProductStockByID(req.ProductID)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Produit %d non trouvé: %v", req.ProductID, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Produit non trouvé"})
|
||||
return
|
||||
}
|
||||
if stock < req.Quantity {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
|
||||
return
|
||||
}
|
||||
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de réserver le stock"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback : recherche par nom+catégorie (compatibilité)
|
||||
if req.NameProduct == "" || req.Category == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "product_id ou name_product+category requis"})
|
||||
return
|
||||
}
|
||||
|
||||
stock, err := database.GetProductStock(req.NameProduct, req.Category)
|
||||
if err != nil {
|
||||
log.Printf("❌ [ADD_PANIER] Produit '%s'/'%s' non trouvé: %v", req.NameProduct, req.Category, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Produit non trouvé"})
|
||||
return
|
||||
}
|
||||
|
||||
if stock < req.Quantity {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Stock insuffisant",
|
||||
"available": stock,
|
||||
})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
|
||||
return
|
||||
}
|
||||
|
||||
// Ajouter au panier (ou mettre à jour si déjà présent)
|
||||
panier, err := db.AddProductInBasket(req.Username, req.NameProduct, req.Quantity, req.Category)
|
||||
panier, err := database.AddProductInBasket(req.Username, req.NameProduct, req.Quantity, req.Category)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier"})
|
||||
log.Printf("❌ [ADD_PANIER] Erreur ajout '%s': %v", req.NameProduct, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible d'ajouter le produit au panier", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Décrémenter le stock
|
||||
if err := db.DecrementProductStock(req.NameProduct, req.Category, req.Quantity); err != nil {
|
||||
if err := database.DecrementProductStock(req.NameProduct, req.Category, req.Quantity); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Impossible de réserver le stock"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"success": true,
|
||||
"message": "Produit ajouté au panier avec succès",
|
||||
"panier": panier,
|
||||
})
|
||||
c.JSON(http.StatusCreated, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -144,7 +163,7 @@ func GetAllBaskets(c *gin.Context) {
|
||||
|
||||
var totalAmount float64
|
||||
for _, item := range baskets {
|
||||
totalAmount += item.Price * item.Quantity
|
||||
totalAmount += item.Price // price = prix total de la ligne (cumul des ajouts)
|
||||
}
|
||||
|
||||
log.Printf("✅ [GET_PANIER] Panier %s: %d articles, total=%.2f€", username, len(baskets), totalAmount)
|
||||
|
||||
@@ -17,5 +17,6 @@ type Client struct {
|
||||
CancellationsCount int `json:"cancellations_count"` // ✅ NOUVEAU
|
||||
LastPenaltyReason string `json:"last_penalty_reason"`
|
||||
MustChangePassword bool `json:"must_change_password"`
|
||||
PushToken string `json:"-"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ func SetupRoutes(router *gin.Engine, database *db.Database, geoService *services
|
||||
cartGroupV1.GET("/notifications", handlers.GetClientNotifications)
|
||||
cartGroupV1.POST("/notifications/read", handlers.MarkNotificationsRead)
|
||||
|
||||
// 📱 PUSH TOKEN
|
||||
cartGroupV1.POST("/push-token", handlers.RegisterPushToken)
|
||||
cartGroupV1.DELETE("/push-token", handlers.UnregisterPushToken)
|
||||
|
||||
// 👤 PROFIL CLIENT - MODIFICATION PAR LE CLIENT
|
||||
cartGroupV1.PUT("/profile/update", handlers.UpdateMyProfile) // ✅ Modifier mon profil
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export const getCart = async (username: string) => {
|
||||
|
||||
export const addToCart = async (cartItem: {
|
||||
username?: string;
|
||||
product_id?: number;
|
||||
name_product: string;
|
||||
category: string;
|
||||
quantity: number;
|
||||
|
||||
@@ -122,6 +122,7 @@ export function CartProvider({ children }: { children: ReactNode }) {
|
||||
try {
|
||||
const response = await apiAddToCart({
|
||||
username,
|
||||
product_id: item.product_id,
|
||||
name_product: cleanName,
|
||||
category: (item.category || "autre").toLowerCase().trim(),
|
||||
quantity: Number(item.quantity),
|
||||
|
||||
Reference in New Issue
Block a user