diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index 5beb96a..17f86a3 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -81,36 +81,22 @@ func main() { mux.HandleFunc("POST /v1/reset-password", authHandler.ResetPassword) mux.HandleFunc("GET /dashboard", userHandler.DashboardView) mux.HandleFunc("GET /v1/user/dashboard", userHandler.DashboardHandler) - mux.HandleFunc("GET /transaction", userHandler.TransactionView) - mux.HandleFunc("GET /topup", userHandler.TopupView) - mux.HandleFunc("GET /profile", userHandler.ProfileView) - mux.HandleFunc("GET /settings", userHandler.SettingsView) - mux.HandleFunc("GET /card", userHandler.CardView) - mux.HandleFunc("GET /v1/user/transactions", userHandler.TransactionsJSONHandler) - mux.HandleFunc("GET /logout", func(w http.ResponseWriter, r *http.Request) { - http.SetCookie(w, &http.Cookie{ - Name: "session_user_id", - Value: "", - Path: "/", - MaxAge: -1, - }) - http.SetCookie(w, &http.Cookie{ - Name: "session_admin_username", - Value: "", - Path: "/", - MaxAge: -1, - }) - http.Redirect(w, r, "/login", http.StatusSeeOther) - }) + //mux.HandleFunc("GET /transaction", userHandler.TransactionView) + //mux.HandleFunc("GET /topup", userHandler.TopupView) + //mux.HandleFunc("GET /profile", userHandler.ProfileView) + //mux.HandleFunc("GET /settings", userHandler.SettingsView) + //mux.HandleFunc("GET /card", userHandler.CardView) + //mux.HandleFunc("GET /v1/user/transactions", userHandler.TransactionsJSONHandler) + //mux.HandleFunc("GET /logout",) // super admin endpoints mux.HandleFunc("GET /admin/platform-overview", adminHanlder.PlatformOverviewView) mux.HandleFunc("GET /admin/merchants", adminHanlder.MerchantManagementView) mux.HandleFunc("GET /admin/terminals", adminHanlder.TerminalRegistryView) mux.HandleFunc("GET /admin/settings", adminHanlder.SystemSettingsView) - - mux.HandleFunc("GET /admin/dashboard", adminHanlder.DashboardView) - mux.HandleFunc("GET /v1/admin/dashboard-data", adminHanlder.DashboardDataHandler) + mux.HandleFunc("POST /v1/admin/merchants/add", adminHanlder.AddMerchantHandler) + mux.HandleFunc("GET /admin/card-inventory", adminHanlder.CardInventoryView) + mux.HandleFunc("GET /v1/admin/card-inventory-data", adminHanlder.CardInventoryDataHandler) mux.HandleFunc("GET /admin/addcard", adminHanlder.AddCardsView) mux.HandleFunc("GET /admin/deactivatecard", adminHanlder.DeactivateView) mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler) diff --git a/backend/internal/admin/addCard.go b/backend/internal/admin/addCard.go index 7815380..b7c6229 100644 --- a/backend/internal/admin/addCard.go +++ b/backend/internal/admin/addCard.go @@ -1,38 +1,27 @@ package admin import ( + "crypto/rand" "database/sql" "encoding/json" + "errors" "fmt" - "math/rand" + "log" + "math/big" "net/http" - "strconv" "strings" "time" jsonwrite "unicard-go/backend/internal/pkg/handler" + "unicard-go/backend/internal/pkg/structs" + + "github.com/go-playground/validator/v10" ) -// Card struct represents a card and its attributes. -type Card struct { - CardUID string - CardNumber string - CardType string - InitialAmount float64 - ExpiryDate string - CreatedAt string -} +// CardRequest struct mapped directly to your frontend JSON payload // AddCardsView renders the addCards.html template after checking the admin session. func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) { fmt.Println("AddCardsView running...") - - // Validate admin session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "addCards.html", nil) } @@ -40,142 +29,102 @@ func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) { func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("AddCardHandler running...") - // Verify session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ - Success: false, - Message: "Unauthorized", - }) - return - } - - var req struct { - CardUID string `json:"cardUID"` - InitialAmount string `json:"initialAmount"` - } + var req structs.CardData - // Try reading JSON body first + // Decode JSON if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - // Fallback to post form values - if err := r.ParseForm(); err == nil { - req.CardUID = r.PostFormValue("cardUID") - req.InitialAmount = r.PostFormValue("initialAmount") - } - } - - cardUID := strings.TrimSpace(req.CardUID) - initialAmount := strings.TrimSpace(req.InitialAmount) - - // Validate required fields - if cardUID == "" || initialAmount == "" { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + log.Println("Error decoding JSON:", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Please fill in all required fields.", + Message: "Invalid request format", }) return } - // Auto-generate card number - fmt.Println("Calling generateCardNumber()...") - cardNumber := h.generateCardNumber() - fmt.Printf("Generated card number: %s\n", cardNumber) - - // Set default card type - cardType := "Regular" - - // Auto-calculate expiry date as 10 years from now - expiryDate := time.Now().AddDate(10, 0, 0).Format("2006-01-02") + cardUID := strings.TrimSpace(req.CardUID) - // Convert Initial Amount (String -> Float64) - amount, err := strconv.ParseFloat(initialAmount, 64) - if err != nil { - fmt.Printf("Error parsing amount: %v\n", err) - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ - Success: false, - Message: "Invalid amount format. Must be a number.", - }) - return - } + // Validate required fields + validate := validator.New() + if err := validate.Struct(req); err != nil { + errorMessage := "Invalid input provided." + var validationErrs validator.ValidationErrors + if errors.As(err, &validationErrs) { + errorMap := map[string]string{ + "CardUID": "Card UID is required.", + "InitialAmount": "Initial amount is required and cannot be negative.", + } + if msg, ok := errorMap[validationErrs[0].Field()]; ok { + errorMessage = msg + } + } - // Check for existing card UID - cardUidExist, err := h.cardUIDExist(cardUID) - if err != nil { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ - Success: false, - Message: "Error checking card UID.", - }) - return - } - if cardUidExist { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Card UID already exists.", + Message: errorMessage, }) return } - // Create a new Card struct - card := Card{ - CardUID: cardUID, - CardNumber: cardNumber, - CardType: cardType, - InitialAmount: amount, - ExpiryDate: expiryDate, - } + // Auto-generate data + cardNumber := h.generateCardNumber() + cardType := "regular" // Lowercase to match your database ENUM + expiryDate := time.Now().AddDate(2, 0, 0).Format("2006-01-02") // 2 years from now unlinked - // Check for existing card number - cardNumExists, err := h.cardNumberExist(card) + // Check for existing UID + uidExists, err := h.cardUIDExist(cardUID) if err != nil { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ Success: false, - Message: "Error checking card number.", + Message: "Error verifying Card UID.", }) return } - if cardNumExists { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + if uidExists { + jsonwrite.WriteJSON(w, http.StatusConflict, jsonwrite.APIResponse{ Success: false, - Message: "Card number already exists.", + Message: "This Card UID is already registered in the system.", }) return } - createdAt := time.Now().Format("2006-01-02 15:04:05") + // Insert card into database (Fixed column name: 'balance') + // We omit created_at because MySQL handles it automatically via DEFAULT CURRENT_TIMESTAMP + query := ` + INSERT INTO cards (card_uid, card_number, card_type, balance, expiry_date, status) + VALUES (?, ?, ?, ?, ?, 'inactive') + ` - // Insert card into database - query := "INSERT INTO cards (card_uid, card_number, card_type, initial_amount, expiry_date, created_at) VALUES (?, ?, ?, ?, ?, ?)" _, err = h.DB.Exec( query, - card.CardUID, - card.CardNumber, - card.CardType, - card.InitialAmount, - card.ExpiryDate, - createdAt, + cardUID, + cardNumber, + cardType, + req.Balance, // Maps to 'balance' + expiryDate, ) + if err != nil { fmt.Println("Error inserting card into database:", err) - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ Success: false, - Message: "Error while adding card.", + Message: "Failed to save card to database.", }) return } - // Successfully added the card - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + // Success Response + jsonwrite.WriteJSON(w, http.StatusCreated, jsonwrite.APIResponse{ Success: true, Message: "Card added successfully!", }) } -//--- HELPER FUNCTIONS --- +// --- HELPER FUNCTIONS --- -func (h *Handler) cardUIDExist(card string) (bool, error) { - var uid string +func (h *Handler) cardUIDExist(uid string) (bool, error) { + var existingUID string query := "SELECT card_uid FROM cards WHERE card_uid = ?" - err := h.DB.QueryRow(query, card).Scan(&uid) + err := h.DB.QueryRow(query, uid).Scan(&existingUID) if err == sql.ErrNoRows { return false, nil } else if err != nil { @@ -184,26 +133,25 @@ func (h *Handler) cardUIDExist(card string) (bool, error) { return true, nil } -func (h *Handler) cardNumberExist(card Card) (bool, error) { - var cardNum string - query := "SELECT card_number FROM cards WHERE card_number = ?" - err := h.DB.QueryRow(query, card.CardNumber).Scan(&cardNum) - if err == sql.ErrNoRows { - return false, nil - } else if err != nil { - return false, err +// Generate Card Number of format YYSS + 10 random digits + DD +func (h *Handler) generateCardNumber() string { + loc, err := time.LoadLocation("Asia/Manila") + if err != nil { + loc = time.Local } - return true, nil -} + now := time.Now().In(loc) -func (h *Handler) generateCardNumber() string { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - now := time.Now() - year := now.Format("06") - month := now.Format("01") - day := now.Format("02") - datePrefix := year + day + month - - randomNum := rng.Intn(1000000000) - return fmt.Sprintf("%s%010d", datePrefix, randomNum) + yy := now.Format("06") + ss := now.Format("05") + dd := now.Format("02") + + max10 := big.NewInt(10000000000) // 10^10 for 10 digits + random10, errRand := rand.Int(rand.Reader, max10) + + randomDigits := "0000000000" // Default fallback format + if errRand == nil { + randomDigits = fmt.Sprintf("%010d", random10.Int64()) + } + + return fmt.Sprintf("%s%s%s%s", yy, ss, randomDigits, dd) } diff --git a/backend/internal/admin/card_inventory.go b/backend/internal/admin/card_inventory.go new file mode 100644 index 0000000..4a9f641 --- /dev/null +++ b/backend/internal/admin/card_inventory.go @@ -0,0 +1,96 @@ +package admin + +import ( + "fmt" + "net/http" + jsonwrite "unicard-go/backend/internal/pkg/handler" +) + +// AdminCard represents a card entry in the admin database +type AdminCard struct { + UserID string `json:"user_id" db:"user_id"` + CardNumber string `json:"card_number" db:"card_number"` + CardType string `json:"card_type" db:"card_type"` + Balance float64 `json:"initial_amount" db:"balance"` + ExpiryDate string `json:"expiry_date" db:"expiry_date"` + Status string `json:"status" db:"status"` + CreatedAt string `json:"created_at" db:"created_at"` +} + +// AdminCardInventoryStats contains statistics about cards +type AdminCardInventoryStats struct { + Total int `json:"total"` + Active int `json:"active"` + Inactive int `json:"inactive"` + Blocked int `json:"blocked"` + Lost int `json:"lost"` +} + +// CardInventoryView handles rendering the static admin dashboard view +func (h *Handler) CardInventoryView(w http.ResponseWriter, r *http.Request) { + fmt.Println("CardInventoryView running...") + + err := h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", nil) + if err != nil { + fmt.Printf("Template execution error: %v\n", err) + } +} + +// CardInventoryDataHandler returns the stats and cards list as JSON +func (h *Handler) CardInventoryDataHandler(w http.ResponseWriter, r *http.Request) { + fmt.Println("CardInventoryDataHandler running...") + + // Fetch Stats + var stats AdminCardInventoryStats + h.DB.QueryRow("SELECT COUNT(*) FROM cards").Scan(&stats.Total) + h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Active'").Scan(&stats.Active) + h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Inactive'").Scan(&stats.Inactive) + h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Blocked'").Scan(&stats.Blocked) + h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Lost'").Scan(&stats.Lost) + + // Fetch Cards + rows, err := h.DB.Query(` + SELECT user_id, card_number, card_type, balance, status, expiry_date, created_at + FROM cards + ORDER BY created_at DESC + `) + if err != nil { + fmt.Printf("Error fetching cards: %v\n", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Failed to fetch cards", + }) + return + } + defer rows.Close() + + var cards []AdminCard + for rows.Next() { + var c AdminCard + err := rows.Scan( + &c.UserID, + &c.CardNumber, + &c.CardType, + &c.Balance, + &c.Status, + &c.ExpiryDate, + &c.CreatedAt, + ) + if err != nil { + fmt.Printf("Error scanning card row: %v\n", err) + continue + } + + cards = append(cards, c) + } + + resp := struct { + Stats AdminCardInventoryStats `json:"stats"` + Cards []AdminCard `json:"cards"` + }{ + Stats: stats, + Cards: cards, + } + + jsonwrite.WriteJSON(w, http.StatusOK, resp) +} diff --git a/backend/internal/admin/dashboard.go b/backend/internal/admin/dashboard.go deleted file mode 100644 index 73426ba..0000000 --- a/backend/internal/admin/dashboard.go +++ /dev/null @@ -1,140 +0,0 @@ -package admin - -import ( - "database/sql" - "encoding/json" - "fmt" - "net/http" -) - -// AdminCard represents a card entry in the admin database -type AdminCard struct { - ID int `json:"id"` - CardUID string `json:"card_uid"` - CardNumber string `json:"card_number"` - CardType string `json:"card_type"` - InitialAmount float64 `json:"initial_amount"` - ExpiryDate string `json:"expiry_date"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - CardHolder string `json:"card_holder"` - UserID string `json:"user_id"` -} - -// AdminStats contains statistics about cards -type AdminStats struct { - Total int `json:"total"` - Active int `json:"active"` - Inactive int `json:"inactive"` - Blocked int `json:"blocked"` - Lost int `json:"lost"` -} - -// DashboardView handles rendering the static admin dashboard view -func (h *Handler) DashboardView(w http.ResponseWriter, r *http.Request) { - fmt.Println("DashboardView running...") - - // Validate admin session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - - err = h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", nil) - if err != nil { - fmt.Printf("Template execution error: %v\n", err) - } -} - -// DashboardDataHandler returns the stats and cards list as JSON -func (h *Handler) DashboardDataHandler(w http.ResponseWriter, r *http.Request) { - fmt.Println("DashboardDataHandler running...") - - //time.Sleep(5 * time.Second) - - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusUnauthorized) - json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"}) - return - } - - // 1. Fetch Stats - var stats AdminStats - h.DB.QueryRow("SELECT COUNT(*) FROM cards").Scan(&stats.Total) - h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Active'").Scan(&stats.Active) - h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Inactive'").Scan(&stats.Inactive) - h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Blocked'").Scan(&stats.Blocked) - h.DB.QueryRow("SELECT COUNT(*) FROM cards WHERE status = 'Lost'").Scan(&stats.Lost) - - // 2. Fetch Cards - rows, err := h.DB.Query(` - SELECT id, card_uid, card_number, card_type, initial_amount, expiry_date, status, created_at, card_holder, user_id - FROM cards - ORDER BY created_at DESC - `) - if err != nil { - fmt.Printf("Error fetching cards: %v\n", err) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(map[string]string{"error": "Failed to fetch cards"}) - return - } - defer rows.Close() - - var cards []AdminCard - for rows.Next() { - var c AdminCard - var cardHolderNull, userIDNull sql.NullString - var createdAtNull sql.NullString - err := rows.Scan( - &c.ID, - &c.CardUID, - &c.CardNumber, - &c.CardType, - &c.InitialAmount, - &c.ExpiryDate, - &c.Status, - &createdAtNull, - &cardHolderNull, - &userIDNull, - ) - if err != nil { - fmt.Printf("Error scanning card row: %v\n", err) - continue - } - - if createdAtNull.Valid { - c.CreatedAt = createdAtNull.String - } else { - c.CreatedAt = "" - } - - if cardHolderNull.Valid { - c.CardHolder = cardHolderNull.String - } else { - c.CardHolder = "Unlinked" - } - - if userIDNull.Valid { - c.UserID = userIDNull.String - } else { - c.UserID = "None" - } - - cards = append(cards, c) - } - - resp := struct { - Stats AdminStats `json:"stats"` - Cards []AdminCard `json:"cards"` - }{ - Stats: stats, - Cards: cards, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) -} diff --git a/backend/internal/admin/deactivateCard.go b/backend/internal/admin/deactivate_card.go similarity index 65% rename from backend/internal/admin/deactivateCard.go rename to backend/internal/admin/deactivate_card.go index 37f542b..c3e7422 100644 --- a/backend/internal/admin/deactivateCard.go +++ b/backend/internal/admin/deactivate_card.go @@ -2,23 +2,23 @@ package admin import ( "encoding/json" + "errors" "fmt" + "log" "net/http" "strings" jsonwrite "unicard-go/backend/internal/pkg/handler" + "unicard-go/backend/internal/pkg/structs" + + "github.com/go-playground/validator/v10" ) -// DeactivateView renders the deactivateCard.html template after verifying session. +// Type alias for CardData to resolve the redeclaration error +type CardData = structs.CardData + func (h *Handler) DeactivateView(w http.ResponseWriter, r *http.Request) { fmt.Println("DeactivateView running...") - - // Validate admin session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - + h.Tpl.ExecuteTemplate(w, "deactivateCard.html", nil) } @@ -26,44 +26,44 @@ func (h *Handler) DeactivateView(w http.ResponseWriter, r *http.Request) { func (h *Handler) DeactivateCardHanlder(w http.ResponseWriter, r *http.Request) { fmt.Println("DeactivateCardHanlder running...") - // Verify session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ + var req CardData + + // Try reading JSON body + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + log.Println("Error decoding JSON:", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Unauthorized", + Message: "Invalid request format", }) return } - var req struct { - CardNumber string `json:"cardNumber"` - Name string `json:"name"` - CardType string `json:"cardType"` - } - - // Try reading JSON body - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - // Fallback to post form values - if err := r.ParseForm(); err == nil { - req.CardNumber = r.PostFormValue("cardNumber") - req.Name = r.PostFormValue("name") - req.CardType = r.PostFormValue("cardType") + validate := validator.New() + if err := validate.Struct(req); err != nil { + errorMessage := "Invalid input provided." + var validationErrs validator.ValidationErrors + if errors.As(err, &validationErrs) { + errorMap := map[string]string{ + "CardNumber": "Card number is required.", + "CardHolder": "Card holder is required.", + "CardType": "Card type is required.", + } + if msg, ok := errorMap[validationErrs[0].Field()]; ok { + errorMessage = msg + } } - } - - cardNumber := strings.TrimSpace(req.CardNumber) - cardHolder := strings.TrimSpace(req.Name) - cardType := strings.TrimSpace(req.CardType) - if cardNumber == "" || cardHolder == "" || cardType == "" { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Please fill in all required fields.", + Message: errorMessage, }) return } + cardNumber := strings.TrimSpace(req.CardNumber) + cardHolder := strings.TrimSpace(req.CardHolder) + cardType := strings.TrimSpace(req.CardType) + // Check if the card exists and is active, then deactivate it ok, err := h.deactivateCardIfActive(cardNumber, cardHolder, cardType) if err != nil { @@ -98,7 +98,7 @@ func (h *Handler) deactivateCardIfActive(cardNumber, cardHolder, cardType string UPDATE cards SET status = 'Blocked' WHERE card_number = ? - AND card_holder = ? + AND user_id = ? AND card_type = ? AND status = 'Active' `, cardNumber, cardHolder, cardType) diff --git a/backend/internal/admin/deleteCard.go b/backend/internal/admin/deleteCard.go index 0a9e4d2..a20d886 100644 --- a/backend/internal/admin/deleteCard.go +++ b/backend/internal/admin/deleteCard.go @@ -8,20 +8,16 @@ import ( jsonwrite "unicard-go/backend/internal/pkg/handler" ) +func (h *Handler) DeleteCardView(w http.ResponseWriter, r *http.Request) { + fmt.Println("DeleteCardView running...") + + h.Tpl.ExecuteTemplate(w, "deleteCard.html", nil) +} + // DeleteCardHandler handles deleting a card by card_number and returns JSON. func (h *Handler) DeleteCardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("DeleteCardHandler running...") - // Verify session - cookie, err := r.Cookie("session_admin_username") - if err != nil || cookie.Value == "" { - jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ - Success: false, - Message: "Unauthorized", - }) - return - } - if r.Method != http.MethodPost { jsonwrite.WriteJSON(w, http.StatusMethodNotAllowed, jsonwrite.APIResponse{ Success: false, diff --git a/backend/internal/admin/super_admin_pages.go b/backend/internal/admin/super_admin_pages.go index b570c32..b74a0c6 100644 --- a/backend/internal/admin/super_admin_pages.go +++ b/backend/internal/admin/super_admin_pages.go @@ -1,16 +1,46 @@ package admin import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" "log" + "math/big" "net/http" + "time" + jsonwrite "unicard-go/backend/internal/pkg/handler" + + "github.com/go-playground/validator/v10" + "golang.org/x/crypto/bcrypt" ) +// AddMerchantRequest represents the payload for adding a new merchant +type AddMerchantRequest struct { + BusinessName string `json:"businessName" validate:"required" db:"business_name"` + BusinessType string `json:"businessType" validate:"required" db:"business_type"` + RegistrationNum string `json:"registrationNum" validate:"required" db:"registration_num"` + BusinessAddress string `json:"businessAddress" validate:"required" db:"business_address"` + OwnerName string `json:"ownerName" validate:"required" db:"owner_name"` + BusinessEmail string `json:"businessEmail" validate:"required,email" db:"business_email"` + BusinessPhone string `json:"businessPhone" validate:"required" db:"business_phone"` + CommissionRate string `json:"commissionRate" validate:"required" db:"commission_rate"` + SettlementName string `json:"settlementName" validate:"required" db:"settlement_name"` + SettlementAccount string `json:"settlementAccount" validate:"required" db:"settlement_account_number"` + SettlementBank string `json:"settlementBank" validate:"required" db:"settlement_bank_name"` +} + +var Validate = validator.New() + // PlatformOverviewView serves the new Super Admin Platform Overview func (h *Handler) PlatformOverviewView(w http.ResponseWriter, r *http.Request) { err := h.Tpl.ExecuteTemplate(w, "platform_overview.html", nil) if err != nil { log.Printf("Template execution error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal Server Error", + }) } } @@ -19,7 +49,10 @@ func (h *Handler) MerchantManagementView(w http.ResponseWriter, r *http.Request) err := h.Tpl.ExecuteTemplate(w, "merchant_management.html", nil) if err != nil { log.Printf("Template execution error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal Server Error", + }) } } @@ -28,7 +61,10 @@ func (h *Handler) TerminalRegistryView(w http.ResponseWriter, r *http.Request) { err := h.Tpl.ExecuteTemplate(w, "hardware_registry.html", nil) if err != nil { log.Printf("Template execution error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal Server Error", + }) } } @@ -37,6 +73,144 @@ func (h *Handler) SystemSettingsView(w http.ResponseWriter, r *http.Request) { err := h.Tpl.ExecuteTemplate(w, "system_settings.html", nil) if err != nil { log.Printf("Template execution error: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal Server Error", + }) + } +} + +// AddMerchantHandler creates a new merchant and its corresponding owner user +func (h *Handler) AddMerchantHandler(w http.ResponseWriter, r *http.Request) { + var req AddMerchantRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: "Invalid JSON payload", + }) + return + } + + err := Validate.Struct(req) + if err != nil { + log.Printf("Validation error: %v", err) + + var validationErrs validator.ValidationErrors + if errors.As(err, &validationErrs) { + firstErr := validationErrs[0] + + fieldMessages := map[string]string{ + "BusinessName": "Business name is required", + "BusinessType": "Business type is required", + "RegistrationNum": "Registration number is required", + "BusinessAddress": "Business address is required", + "OwnerName": "Owner name is required", + "BusinessEmail": "A valid business email is required", + "BusinessPhone": "Business phone number is required", + "CommissionRate": "Commission rate is required", + "SettlementName": "Settlement name is required", + "SettlementAccount": "Settlement account number is required", + "SettlementBank": "Settlement bank name is required", + } + + msg := "Validation failed on field: " + firstErr.Field() + if customMsg, ok := fieldMessages[firstErr.Field()]; ok { + msg = customMsg + } + + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: msg, + }) + return + } + + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: "Validation error", + }) + return + } + + // Generate IDs (Format: YYMMminsecxxxxx where xxxxx is 5 random numbers) + timestamp := time.Now().Format("06010405") // YYMMDDHH + + nUser, _ := rand.Int(rand.Reader, big.NewInt(100000)) + userID := fmt.Sprintf("UNI-%s%04d", timestamp, nUser.Int64()) + + nMerchant, _ := rand.Int(rand.Reader, big.NewInt(100000)) + merchantID := fmt.Sprintf("MCH-%s%04d", timestamp, nMerchant.Int64()) + + // Create user for the merchant owner + hashedPassword, err := bcrypt.GenerateFromPassword([]byte("TempPass123!"), bcrypt.DefaultCost) + if err != nil { + log.Printf("Error hashing password: %v", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Failed to secure user credentials", + }) + return + } + + tx, err := h.DB.Begin() + if err != nil { + log.Printf("Error starting tx: %v", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Database error", + }) + return + } + + // Insert User + userStmt := `INSERT INTO users (user_id, username, name, email, phone_number, password_hash, role, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + // Using business email as username for simplicity, you could use a dedicated username field + username := req.BusinessEmail + _, err = tx.Exec(userStmt, userID, username, req.OwnerName, req.BusinessEmail, req.BusinessPhone, string(hashedPassword), "merchant_admin", "active") + if err != nil { + tx.Rollback() + log.Printf("Error creating user: %v", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: "Failed to create user account (email or phone might already exist)", + }) + return } + + // Insert Merchant + merchStmt := `INSERT INTO merchants ( + merchant_id, business_name, business_type, business_registration_number, business_address, + owner_user_id, owner_name, business_email, business_phone, commission_rate, + settlement_account_name, settlement_account_number, settlement_bank_name, status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + + _, err = tx.Exec(merchStmt, + merchantID, req.BusinessName, req.BusinessType, req.RegistrationNum, req.BusinessAddress, + userID, req.OwnerName, req.BusinessEmail, req.BusinessPhone, req.CommissionRate, + req.SettlementName, req.SettlementAccount, req.SettlementBank, "active", + ) + + if err != nil { + tx.Rollback() + log.Printf("Error creating merchant: %v", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: "Failed to create merchant profile (registration num or email might exist)", + }) + return + } + + if err := tx.Commit(); err != nil { + log.Printf("Error committing tx: %v", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Failed to finalize creation", + }) + return + } + + jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + Success: true, + Message: "Merchant onboarded successfully", + }) } diff --git a/backend/internal/auth/repository.go b/backend/internal/auth/repository.go index 4dfafed..2245d38 100644 --- a/backend/internal/auth/repository.go +++ b/backend/internal/auth/repository.go @@ -129,7 +129,7 @@ func (h *Handler) GenerateCardID() (string, error) { func (h *Handler) GetInitialBalance(cardNumber string) (float64, error) { var initialBalance float64 // to hold the initial balance - query := "SELECT initial_amount FROM cards WHERE card_number = ?" + query := "SELECT balance FROM cards WHERE card_number = ?" err := h.DB.QueryRow(query, cardNumber).Scan(&initialBalance) if err != nil { log.Printf("GetInitialBalance error for card %s: %v", cardNumber, err) @@ -147,7 +147,7 @@ func (h *Handler) isPhoneExist(phone string) (bool, error) { var existingPhone string // Check query - query := "SELECT phone FROM users WHERE phone = ?" + query := "SELECT phone_number FROM users WHERE phone_number = ?" err := h.DB.QueryRow(query, phone).Scan(&existingPhone) if err == sql.ErrNoRows { return false, nil @@ -168,7 +168,7 @@ func (h *Handler) isFullNameExist(fullName string) (bool, error) { var existingName string // Check query - query := "SELECT full_name FROM users WHERE full_name = ?" + query := "SELECT name FROM users WHERE name = ?" err := h.DB.QueryRow(query, fullName).Scan(&existingName) if err == sql.ErrNoRows { return false, nil diff --git a/backend/internal/auth/signup.go b/backend/internal/auth/signup.go index 2aa824c..4f8dfa0 100644 --- a/backend/internal/auth/signup.go +++ b/backend/internal/auth/signup.go @@ -22,13 +22,14 @@ const ( // Create a struct to catch the incoming JSON from the frontend type SignupRequest struct { - ID string `json:"id,omitempty" db:"ID"` - FirstName string `json:"first_name" db:"first_name" validate:"required"` - LastName string `json:"last_name" db:"last_name" validate:"required"` - CardNumber string `json:"card_number" db:"card_number" validate:"required,numeric,len=16"` - Password string `json:"password" db:"password_hash" validate:"required,min=8"` - Email string `json:"email" db:"email" validate:"required,email"` - ContactNumber string `json:"contact_number" db:"phone" validate:"required,numeric,len=11"` + ID string `json:"id,omitempty"` + FirstName string `json:"first_name" validate:"required"` + LastName string `json:"last_name" validate:"required"` + Name string `json:"name" db:"name"` + CardNumber string `json:"card_number" validate:"required,numeric,len=16"` + Password string `json:"password" validate:"required,min=8"` + Email string `json:"email" validate:"required,email"` + ContactNumber string `json:"contact_number" validate:"required,numeric,len=11"` } // User struct to hold signup data (Keep your existing one) @@ -36,15 +37,17 @@ type SignupRequest struct { type User struct { UserID string `db:"user_id"` Username string `db:"username"` - Fullname string `db:"full_name"` + Name string `db:"name"` Email string `db:"email"` - Phone string `db:"phone"` + Phone string `db:"phone_number"` CardNumber string `db:"card_number"` Password string `db:"password_hash"` - CardID string `db:"card_id"` - Usertype string `db:"user_type"` + UserType string `db:"role"` Balance float64 `db:"balance"` CreatedAt string `db:"created_at"` + Role string `db:"role"` + Status string `db:"status"` + RegionID string `db:"region_id"` } type CheckDetailsRequest struct { @@ -130,7 +133,7 @@ func (h *Handler) CheckCardHandler(w http.ResponseWriter, r *http.Request) { return } - if status != "Inactive" { + if status != "inactive" { jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, Message: "Card is invalid", @@ -187,20 +190,16 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { errorMessage := "Invalid input provided." var validationErrs validator.ValidationErrors if errors.As(err, &validationErrs) { - firstErr := validationErrs[0] - switch firstErr.Field() { - case "FirstName": - errorMessage = "First name is required." - case "LastName": - errorMessage = "Last name is required." - case "Email": - errorMessage = "Please provide a valid email address." - case "ContactNumber": - errorMessage = "Contact number must be exactly 11 digits." - case "CardNumber": - errorMessage = "Card number must be exactly 16 digits." - case "Password": - errorMessage = "Password must be at least 8 characters long." + errorMap := map[string]string{ + "FirstName": "First name is required.", + "LastName": "Last name is required.", + "Email": "Please provide a valid email address.", + "ContactNumber": "Contact number must be exactly 11 digits.", + "CardNumber": "Card number must be exactly 16 digits.", + "Password": "Password must be at least 8 characters long.", + } + if msg, ok := errorMap[validationErrs[0].Field()]; ok { + errorMessage = msg } } @@ -223,18 +222,6 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { } log.Printf("Password hashed successfully: %v", hashedPassword) - // Generate Username - generatedUsername, err := h.GenerateUniqueUsername() - if err != nil { - log.Printf("Error generating username: %v", err) - jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ - Success: false, - Message: "System error generating username. Please try again.", - }) - return - } - log.Printf("Generated Username: %v", generatedUsername) - // Generate IDs generateUserId, err := h.GenerateUserID() if err != nil { @@ -246,14 +233,6 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { return } - generateCardID, err := h.GenerateCardID() - if err != nil { - log.Printf("Error generating CardID: %v", err) - jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ - Success: false, Message: "System error generating CardID."}) - return - } - // Get Timestamp createdAt, err := CurrentTimestamp() if err != nil { @@ -280,16 +259,17 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { // Build User struct user := User{ UserID: fmt.Sprintf("%d", generateUserId), - CardID: generateCardID, - Usertype: UserTypeRegular, - Username: generatedUsername, - Fullname: req.FirstName + " " + req.LastName, + UserType: UserTypeRegular, + Username: req.FirstName, + Name: req.FirstName + " " + req.LastName, CardNumber: req.CardNumber, Password: hashedPassword, Email: req.Email, Phone: req.ContactNumber, CreatedAt: createdAt, Balance: balance, + Role: "Customer", + Status: "active", } // Begin transaction: insert user + activate card atomically @@ -306,11 +286,11 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { // Insert User insertQuery := `INSERT INTO users - (user_id, username, full_name, email, phone, password_hash, card_id, card_number, user_type, balance, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + (user_id, username, name, email, phone_number, password_hash, role, status, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` _, err = tx.ExecContext(ctx, insertQuery, - user.UserID, user.Username, user.Fullname, user.Email, user.Phone, - user.Password, user.CardID, user.CardNumber, user.Usertype, user.Balance, user.CreatedAt, + user.UserID, user.Username, user.Name, user.Email, user.Phone, + user.Password, user.Role, user.Status, user.CreatedAt, ) if err != nil { log.Printf("Error inserting user: %v", err) @@ -320,20 +300,21 @@ func (h *Handler) SignupHandler(w http.ResponseWriter, r *http.Request) { }) return } - log.Printf("User record inserted: %v", user.UserID) + log.Printf("User record inserted: %v", user.Name) // Activate Card and Link User Details - // Set expiry date to 10 years from now in expiry_date column + // Set expiry date to 5 years from now in expiry_date column updateCardQuery := ` UPDATE cards - SET status = 'Active', + SET status = 'active', user_id = ?, - card_holder = ?, - linked_at = CURRENT_TIMESTAMP, - expiry_date = DATE_ADD(CURRENT_DATE, INTERVAL 10 YEAR) + card_type = 'regular', + linked_at = CURRENT_TIMESTAMP, + updated_at = CURRENT_TIMESTAMP, + expiry_date = DATE_ADD(CURRENT_DATE, INTERVAL 5 YEAR) WHERE card_number = ?` - _, err = tx.ExecContext(ctx, updateCardQuery, user.UserID, user.Fullname, user.CardNumber) + _, err = tx.ExecContext(ctx, updateCardQuery, user.UserID, user.CardNumber) if err != nil { log.Printf("Error activating card for card_number %s: %v", user.CardNumber, err) diff --git a/backend/internal/pkg/structs/card_struct.go b/backend/internal/pkg/structs/card_struct.go new file mode 100644 index 0000000..26c0a9b --- /dev/null +++ b/backend/internal/pkg/structs/card_struct.go @@ -0,0 +1,9 @@ +package structs + +type CardData struct { + CardUID string `json:"card_uid" db:"card_uid" validate:"required"` + CardNumber string `json:"cardNumber" db:"card_number" validate:"required"` + CardHolder string `json:"cardHolder" db:"user_id" validate:"required"` + CardType string `json:"cardType" db:"card_type" validate:"required"` + Balance float64 `json:"initial_amount" db:"balance" validate:"required,min=0"` +} diff --git a/backend/internal/user/dashboard.go b/backend/internal/user/dashboard.go index 7412abb..46a21ea 100644 --- a/backend/internal/user/dashboard.go +++ b/backend/internal/user/dashboard.go @@ -38,13 +38,7 @@ type DashboardUser struct { func (h *Handler) DashboardView(w http.ResponseWriter, r *http.Request) { fmt.Println("Dashboard view is running...") - // Check if session cookie is present - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - fmt.Println("No user session found in view, redirecting to login") - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } + // Check if session cookie is present (Removed) h.Tpl.ExecuteTemplate(w, "dashboard.html", nil) } @@ -52,17 +46,8 @@ func (h *Handler) DashboardView(w http.ResponseWriter, r *http.Request) { func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("Dashboard JSON handler is running...") - // Get session cookie - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - fmt.Println("No user session found, returning unauthorized JSON") - jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ - Success: false, - Message: "Unauthorized", - }) - return - } - userID := cookie.Value + // Get session cookie (Removed) + userID := "UNI-060104051234" // Dummy user ID for testing // Fetch user and card details var ( @@ -80,35 +65,30 @@ func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { ) stmt := ` SELECT - u.id, - u.username, - u.full_name, - u.email, - u.phone, - u.user_type, - u.balance, - u.loyalty_points, - u.card_number, - u.status - FROM users u - LEFT JOIN cards c - ON u.card_number = c.card_number - WHERE u.user_id = ? + u.id, + u.username, + u.name, + u.email, + COALESCE(u.phone_number, ''), + u.role, + COALESCE(c.balance, 0), + COALESCE(c.loyalty_points, 0), + COALESCE(c.card_number, ''), + COALESCE(c.expiry_date, ''), + COALESCE(c.status, '') + FROM users u + LEFT JOIN cards c + ON u.user_id = c.user_id + WHERE u.user_id = ? ` - err = h.DB.QueryRow(stmt, userID).Scan(&id, &username, &fullName, &email, &phone, &userType, &balance, &loyaltyPoints, &cardNumber, &cardStatus) + err := h.DB.QueryRow(stmt, userID).Scan(&id, &username, &fullName, &email, &phone, &userType, &balance, &loyaltyPoints, &cardNumber, &expiryDate, &cardStatus) if err != nil { if err == sql.ErrNoRows { fmt.Printf("User %s not found in DB\n", userID) } else { fmt.Printf("Error fetching user %s from DB: %v\n", userID, err) } - // Clear invalid session cookie and return unauthorized response - http.SetCookie(w, &http.Cookie{ - Name: "session_user_id", - Value: "", - Path: "/", - MaxAge: -1, - }) + // Clear invalid session cookie (Removed) jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ Success: false, Message: "Unauthorized: User not found", @@ -140,7 +120,15 @@ func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { } // Fetch recent transactions - rows, err := h.DB.Query("SELECT created_at, description, transaction_type, amount FROM transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT 5", userID) + txnQuery := ` + SELECT t.created_at, m.business_name, t.transaction_type, t.amount + FROM transactions t + JOIN cards c ON t.card_number = c.card_number + JOIN merchants m ON t.merchant_id = m.id + WHERE c.user_id = ? + ORDER BY t.created_at DESC LIMIT 5 + ` + rows, err := h.DB.Query(txnQuery, userID) var transactions []Transaction if err == nil { defer rows.Close() diff --git a/backend/internal/user/pages.go b/backend/internal/user/pages.go deleted file mode 100644 index a1efeb4..0000000 --- a/backend/internal/user/pages.go +++ /dev/null @@ -1,105 +0,0 @@ -package user - -import ( - "fmt" - "net/http" - - jsonwrite "unicard-go/backend/internal/pkg/handler" -) - -func (h *Handler) TransactionView(w http.ResponseWriter, r *http.Request) { - fmt.Println("Transaction view is running...") - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "transaction.html", nil) -} - -func (h *Handler) TopupView(w http.ResponseWriter, r *http.Request) { - fmt.Println("Topup view is running...") - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "topup.html", nil) -} - -func (h *Handler) ProfileView(w http.ResponseWriter, r *http.Request) { - fmt.Println("Profile view is running...") - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "profile.html", nil) -} - -func (h *Handler) SettingsView(w http.ResponseWriter, r *http.Request) { - fmt.Println("Settings view is running...") - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "settings.html", nil) -} - -func (h *Handler) CardView(w http.ResponseWriter, r *http.Request) { - fmt.Println("Card view is running...") - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return - } - h.Tpl.ExecuteTemplate(w, "card.html", nil) -} - -func (h *Handler) TransactionsJSONHandler(w http.ResponseWriter, r *http.Request) { - fmt.Println("Transactions JSON handler is running...") - - // Get session cookie - cookie, err := r.Cookie("session_user_id") - if err != nil || cookie.Value == "" { - jsonwrite.WriteJSON(w, http.StatusUnauthorized, jsonwrite.APIResponse{ - Success: false, - Message: "Unauthorized", - }) - return - } - userID := cookie.Value - - // Fetch all transactions - rows, err := h.DB.Query("SELECT created_at, description, transaction_type, amount FROM transactions WHERE user_id = ? ORDER BY created_at DESC", userID) - var transactions []Transaction - if err == nil { - defer rows.Close() - for rows.Next() { - var t Transaction - var createdAt string - if err := rows.Scan(&createdAt, &t.Description, &t.Type, &t.Amount); err == nil { - t.Date = formatDate(createdAt) // Using formatDate from dashboard.go - transactions = append(transactions, t) - } - } - } else { - fmt.Printf("Error fetching transactions: %v\n", err) - jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ - Success: false, - Message: "Failed to load transactions", - }) - return - } - - // If no transactions, ensure we send an empty array, not null - if transactions == nil { - transactions = []Transaction{} - } - - jsonwrite.WriteJSON(w, http.StatusOK, map[string]interface{}{ - "success": true, - "transactions": transactions, - }) -} diff --git a/frontend/assets/js/admin/admin-dasboard.js b/frontend/assets/js/admin/admin-dasboard.js new file mode 100644 index 0000000..131a71f --- /dev/null +++ b/frontend/assets/js/admin/admin-dasboard.js @@ -0,0 +1,319 @@ +// Modal Control Functions +function openDeactivateModal(num, holder, type) { + document.getElementById('deactivate-card-number').value = num; + document.getElementById('deactivate-card-holder').value = holder; + document.getElementById('deactivate-card-type').value = type; + + document.getElementById('deactivate-modal-num').innerText = num; + document.getElementById('deactivate-modal-holder').innerText = holder; + document.getElementById('deactivate-modal-type').innerText = type; + + document.getElementById('deactivate-modal').classList.remove('hidden'); +} + +function closeDeactivateModal() { + document.getElementById('deactivate-modal').classList.add('hidden'); +} + +function openDeleteModal(num) { + document.getElementById('delete-card-number').value = num; + document.getElementById('delete-modal-num').innerText = num; + document.getElementById('delete-modal').classList.remove('hidden'); +} + +function closeDeleteModal() { + document.getElementById('delete-modal').classList.add('hidden'); +} + +function openLogoutModal() { + document.getElementById('logout-modal').classList.remove('hidden'); +} + +function closeLogoutModal() { + document.getElementById('logout-modal').classList.add('hidden'); +} + +// Pagination and Search Configuration +const rowsPerPage = 4; +let currentPage = 1; +let allCards = []; +let filteredCards = []; + +function renderCards() { + const totalRows = filteredCards.length; + const totalPages = Math.ceil(totalRows / rowsPerPage); + + if (currentPage > totalPages) { + currentPage = Math.max(1, totalPages); + } + + const startIdx = (currentPage - 1) * rowsPerPage; + const endIdx = Math.min(startIdx + rowsPerPage, totalRows); + + const tbody = document.getElementById('table-body'); + tbody.innerHTML = ''; + + const pageCards = filteredCards.slice(startIdx, endIdx); + if (pageCards.length === 0) { + tbody.innerHTML = ` +
Core System
-Legacy Operations
++ Legacy Operations