diff --git a/backend/Controllers/UsersController.cs b/backend/Controllers/UsersController.cs index 8b6156b..5dae2e2 100644 --- a/backend/Controllers/UsersController.cs +++ b/backend/Controllers/UsersController.cs @@ -36,4 +36,17 @@ public async Task GetByUsername(string username) } return Ok(user); } + + [HttpPut("me")] + public async Task UpdateProfile([FromBody] UserUpdateDto dto) + { + var uidClaim = User.FindFirst("uid")?.Value; + if (uidClaim == null) + return Unauthorized(); + + var userId = Guid.Parse(uidClaim); + + await _service.UpdateProfileAsync(userId, dto); + return NoContent(); + } } diff --git a/backend/DTOs/User/UserDTO.cs b/backend/DTOs/User/UserDTO.cs index f412fb0..d7189e4 100644 --- a/backend/DTOs/User/UserDTO.cs +++ b/backend/DTOs/User/UserDTO.cs @@ -29,3 +29,11 @@ public class UserResponseDto public string Role { get; set; } } +public class UserUpdateDto +{ + public string FirstName { get; set; } = null!; + public string LastName { get; set; } = null!; + public string Email { get; set; } = null!; + public string? Phone { get; set; } + public string? Address { get; set; } +} diff --git a/backend/Middleware/GlobalLogger.cs b/backend/Middleware/GlobalLogger.cs index 38c93b3..3eee924 100644 --- a/backend/Middleware/GlobalLogger.cs +++ b/backend/Middleware/GlobalLogger.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using System.IO; +using Npgsql; +using System.Diagnostics; +using System.Security.Claims; using System.Text; namespace backend.Middleware; @@ -10,7 +12,9 @@ public class GlobalRequestLoggingMiddleware private readonly RequestDelegate _next; private readonly ILogger _logger; - public GlobalRequestLoggingMiddleware(RequestDelegate next, ILogger logger) + public GlobalRequestLoggingMiddleware( + RequestDelegate next, + ILogger logger) { _next = next; _logger = logger; @@ -18,29 +22,97 @@ public GlobalRequestLoggingMiddleware(RequestDelegate next, ILogger c.Type == "uid")?.Value; + var username = context.User?.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value + ?? context.User?.Claims?.FirstOrDefault(c => c.Type == "sub")?.Value; - try + using (_logger.BeginScope(new Dictionary { - await _next(context); // call next middleware/controller - _logger.LogInformation("Response Status: {StatusCode}", context.Response.StatusCode); - } - catch (Exception ex) + ["CorrelationId"] = correlationId + })) { - _logger.LogError(ex, "Unhandled exception for request {Method} {Path}", context.Request.Method, context.Request.Path); - throw; + // ---- REQUEST LOGGING ---- + _logger.LogInformation( + "Incoming Request | {Method} {Path} | Auth={Auth} | UserId={UserId} | Username={Username}", + context.Request.Method, + context.Request.Path, + isAuthenticated, + userId ?? "anonymous", + username ?? "anonymous" + ); + + // Log request body (SAFE endpoints only) + if ((context.Request.Method == HttpMethods.Post || + context.Request.Method == HttpMethods.Put) && + !context.Request.Path.StartsWithSegments("/auth")) + { + context.Request.EnableBuffering(); + + using var reader = new StreamReader( + context.Request.Body, + Encoding.UTF8, + leaveOpen: true); + + var body = await reader.ReadToEndAsync(); + context.Request.Body.Position = 0; + + if (!string.IsNullOrWhiteSpace(body)) + { + _logger.LogInformation("Request Body: {Body}", body); + } + } + + try + { + await _next(context); // Continue pipeline + + stopwatch.Stop(); + + // ---- RESPONSE LOGGING ---- + _logger.LogInformation( + "Response | {Method} {Path} | Status={StatusCode} | Time={ElapsedMs}ms", + context.Request.Method, + context.Request.Path, + context.Response.StatusCode, + stopwatch.ElapsedMilliseconds + ); + } + catch (PostgresException pgEx) + { + stopwatch.Stop(); + + // ---- DATABASE ERROR LOGGING ---- + _logger.LogError( + pgEx, + "Postgres Error | SqlState={SqlState} | Constraint={Constraint} | Message={Message} | Time={ElapsedMs}ms", + pgEx.SqlState, + pgEx.ConstraintName, + pgEx.MessageText, + stopwatch.ElapsedMilliseconds + ); + + throw; + } + catch (Exception ex) + { + stopwatch.Stop(); + + // ---- UNHANDLED ERROR LOGGING ---- + _logger.LogError( + ex, + "Unhandled Exception | {Method} {Path} | Time={ElapsedMs}ms", + context.Request.Method, + context.Request.Path, + stopwatch.ElapsedMilliseconds + ); + + throw; + } } } } diff --git a/backend/Repositories/User/IUserRepository.cs b/backend/Repositories/User/IUserRepository.cs index 2a67877..3af3bf6 100644 --- a/backend/Repositories/User/IUserRepository.cs +++ b/backend/Repositories/User/IUserRepository.cs @@ -1,4 +1,5 @@ using backend.Models; +using backend.DTOs; namespace backend.Repositories; @@ -8,7 +9,9 @@ public interface IUserRepository Task GetByEmailAsync(string email); Task GetByLoginAsync(string login); Task> GetAllAsync(); - Task CreateAsync(User user); + Task CreateAsync(User user); + Task GetByIdAsync(Guid id); + Task UpdateProfileAsync(Guid id, UserUpdateDto dto); } diff --git a/backend/Repositories/User/UserRepository.cs b/backend/Repositories/User/UserRepository.cs index 1f35642..11d3eaf 100644 --- a/backend/Repositories/User/UserRepository.cs +++ b/backend/Repositories/User/UserRepository.cs @@ -1,6 +1,7 @@ using System.Data; using Dapper; using backend.Models; +using backend.DTOs; namespace backend.Repositories; @@ -40,7 +41,7 @@ public async Task> GetAllAsync() } - public async Task CreateAsync(User user) + public async Task CreateAsync(User user) { const string sql = @" INSERT INTO ""user"" (username, password, last_name, first_name, email, phone, address, role) @@ -48,7 +49,37 @@ public async Task CreateAsync(User user) RETURNING u_id; "; - return await _db.ExecuteScalarAsync(sql, user); + user.UId = await _db.ExecuteScalarAsync(sql, user); + } + + public async Task GetByIdAsync(Guid id) + { + const string sql = @"SELECT * FROM ""user"" WHERE u_id = @Id;"; + return await _db.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + + public async Task UpdateProfileAsync(Guid id, UserUpdateDto dto) + { + const string sql = @" + UPDATE ""user"" + SET + first_name = @FirstName, + last_name = @LastName, + email = @Email, + phone = @Phone, + address = @Address + WHERE u_id = @Id; + "; + + await _db.ExecuteAsync(sql, new + { + Id = id, + dto.FirstName, + dto.LastName, + dto.Email, + dto.Phone, + dto.Address + }); } } diff --git a/backend/Services/User/IUserService.cs b/backend/Services/User/IUserService.cs index eb98eca..bb30edc 100644 --- a/backend/Services/User/IUserService.cs +++ b/backend/Services/User/IUserService.cs @@ -10,6 +10,7 @@ public interface IUserService Task GetUserByUsernameAsync(string username); Task> GetAllUsersAsync(); Task RefreshAsync(string refreshToken); + Task UpdateProfileAsync(Guid userId, UserUpdateDto dto); } diff --git a/backend/Services/User/UserService.cs b/backend/Services/User/UserService.cs index 828a135..ac21e90 100644 --- a/backend/Services/User/UserService.cs +++ b/backend/Services/User/UserService.cs @@ -47,6 +47,8 @@ public async Task RegisterAsync(UserRegisterDto dto) user.Password = _passwordHasher.HashPassword(user, dto.Password); await _repo.CreateAsync(user); + if (user.UId == Guid.Empty) + throw new Exception("User ID was not generated"); var accessToken = GenerateJwt(user, false); // short-lived access token var refreshToken = GenerateJwt(user, true); // long-lived refresh token @@ -84,6 +86,8 @@ public async Task RegisterAdminAsync(UserRegisterDto dto) user.Password = _passwordHasher.HashPassword(user, dto.Password); await _repo.CreateAsync(user); + if (user.UId == Guid.Empty) + throw new Exception("User ID was not generated"); var accessToken = GenerateJwt(user, false); // short-lived access token var refreshToken = GenerateJwt(user, true); // long-lived refresh token @@ -233,4 +237,19 @@ public async Task> GetAllUsersAsync() }; } + public async Task UpdateProfileAsync(Guid userId, UserUpdateDto dto) + { + var existingUser = await _repo.GetByIdAsync(userId); + if (existingUser == null) + throw new Exception("User not found"); + + // Optional: prevent email duplication + var emailOwner = await _repo.GetByEmailAsync(dto.Email); + if (emailOwner != null && emailOwner.UId != userId) + throw new Exception("Email already in use"); + + await _repo.UpdateProfileAsync(userId, dto); + } + + } diff --git a/docs/ERDdiagram.svg b/docs/ERDdiagram.svg deleted file mode 100644 index 60410fa..0000000 --- a/docs/ERDdiagram.svg +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - -BookstoreMySQL - - - -Publisher - -Publisher - - - -pub_id - -<u>publisher_id</u> - - - -Publisher->pub_id - - - - -pub_name - -publisher_name - - - -Publisher->pub_name - - - - -pub_address - -address - - - -Publisher->pub_address - - - - -Published_By - -Published_By - - - -Publisher->Published_By - -1 - - - -Publisher_HasPhone - -Has - - - -Publisher->Publisher_HasPhone - -1 - - - -Author - -Author - - - -auth_id - -<u>author_id</u> - - - -Author->auth_id - - - - -auth_name - -author_name - - - -Author->auth_name - - - - -Written_By - -Written_By - - - -Author->Written_By - -N - - - -Book - -Book - - - -ReplenishmentOrder - -ReplenishmentOrder - - - -Book->ReplenishmentOrder - -1 - - - -isbn - -<u>isbn</u> - - - -Book->isbn - - - - -title - -title - - - -Book->title - - - - -pub_year - -pub_year - - - -Book->pub_year - - - - -price - -price - - - -Book->price - - - - -category - -category - - - -Book->category - - - - -stock - -stock - - - -Book->stock - - - - -threshold - -threshold - - - -Book->threshold - - - - -Book->Published_By - -N - - - -Book->Written_By - -N - - - -Contains_Cart - -Contains - - - -Book->Contains_Cart - -N - - - -Contains_Order - -Contains - - - -Book->Contains_Order - -N - - - -User - -User - - - -u_id - -<u>u_id</u> - - - -User->u_id - - - - -username - -username - - - -User->username - - - - -password - -password - - - -User->password - - - - -last_name - -last_name - - - -User->last_name - - - - -first_name - -first_name - - - -User->first_name - - - - -email - -email - - - -User->email - - - - -user_phone - -phone - - - -User->user_phone - - - - -address - -address - - - -User->address - - - - -role - -role - - - -User->role - - - - -Places_Order - -Places - - - -User->Places_Order - -1 - - - -Has_Cart - -Has - - - -User->Has_Cart - -1 - - - -Has_CreditCard - -Has - - - -User->Has_CreditCard - -1 - - - -ReplenishmentOrder->Book - -N - - - -ro_id - -<u>order_id</u> - - - -ReplenishmentOrder->ro_id - - - - -ro_date - -order_date - - - -ReplenishmentOrder->ro_date - - - - -ro_quantity - -quantity - - - -ReplenishmentOrder->ro_quantity - - - - -ro_status - -status - - - -ReplenishmentOrder->ro_status - - - - -CustomerOrder - -CustomerOrder - - - -co_id - -<u>order_id</u> - - - -CustomerOrder->co_id - - - - -co_date - -order_date - - - -CustomerOrder->co_date - - - - -co_total - -total_price - - - -CustomerOrder->co_total - - - - -CustomerOrder->Contains_Order - -1 - - - -Cart - -Cart - - - -cart_id - -<u>cart_id</u> - - - -Cart->cart_id - - - - -Cart->Contains_Cart - -1 - - - -CreditCard - -CreditCard - - - -card_id - -<u>card_id</u> - - - -CreditCard->card_id - - - - -cc_u_id - -u_id - - - -CreditCard->cc_u_id - - - - -cardholder_name - -cardholder_name - - - -CreditCard->cardholder_name - - - - -expiration_date - -expiration_date - - - -CreditCard->expiration_date - - - - -encrypted_card_number - -encrypted_card_number - - - -CreditCard->encrypted_card_number - - - - -last4 - -last4 - - - -CreditCard->last4 - - - - -keyver - -keyver - - - -CreditCard->keyver - - - - -PublisherPhone - -PublisherPhone - - - -phone - -phone - - - -PublisherPhone->phone - - - - -BookAuthor - - -BookAuthor - - - -ba_isbn - -isbn - - - -BookAuthor->ba_isbn - - - - -ba_author_id - -author_id - - - -BookAuthor->ba_author_id - - - - -CartItem - - -CartItem - - - -ci_quantity - -quantity - - - -CartItem->ci_quantity - - - - -CustomerOrderItem - - -CustomerOrderItem - - - -coi_quantity - -quantity - - - -CustomerOrderItem->coi_quantity - - - - -coi_price - -price - - - -CustomerOrderItem->coi_price - - - - -Written_By->BookAuthor - - - - -Contains_Cart->CartItem - - - - -Contains_Order->CustomerOrderItem - - - - -Places_Order->CustomerOrder - - - - -Has_Cart->Cart - - - - -Has_CreditCard->CreditCard - - - - -Publisher_HasPhone->PublisherPhone - -N - - - diff --git a/docs/UI_Screen_Logic_Description.md b/docs/UI_Screen_Logic_Description.md index 241b3d7..124b5b9 100644 --- a/docs/UI_Screen_Logic_Description.md +++ b/docs/UI_Screen_Logic_Description.md @@ -648,7 +648,234 @@ - Verifies authentication before rendering - Shows loading message during verification -**Note:** The actual book management, publisher orders, and reports pages are referenced but not implemented in the current codebase. The dashboard serves as a navigation hub to these future admin features. +--- + +### 12. Manage Books Page (`/admin/books`) +**File:** [frontend/app/admin/books/page.tsx](../frontend/app/admin/books/page.tsx) + +**Purpose:** Admin interface for managing book inventory and details + +**Logic Flow:** +1. **Authentication & Authorization:** + - Verifies user is authenticated with Admin role + - Redirects non-admin users to login + +2. **Data Loading:** + - Fetches all books with complete details + - Loads all authors for author assignment + - Removes duplicate ISBNs from display + +3. **Search & Filter:** + - Real-time search across book title, ISBN, author names, and category + - Client-side filtering for immediate results + - Search bar with magnifying glass icon + +4. **Book Management Operations:** + - **Edit Book:** + - Inline editing with expandable form + - Editable fields: title, publisher ID, publication year, price, category, stock, threshold + - Author assignment with multi-select checkboxes + - Real-time validation + - Save/Cancel buttons with loading states + + - **Delete Book:** + - Trash icon with confirmation dialog + - Shows loading state during deletion + - Removes book from inventory entirely + +5. **Book Display:** + - Tabular layout with book cover thumbnails + - Shows ISBN, title, authors, price, stock, category + - Stock level indicators (low stock warnings) + - Publication year and threshold information + +6. **Navigation:** + - "Add New Book" button linking to creation form + - Back to Admin Dashboard + - Breadcrumb navigation + +7. **Error & Success Handling:** + - Success messages for updates and deletions + - Error handling for failed operations + - Form validation feedback + +--- + +### 13. Add New Book Page (`/admin/books/new`) +**File:** [frontend/app/admin/books/new/page.tsx](../frontend/app/admin/books/new/page.tsx) + +**Purpose:** Create new books in the inventory system + +**Logic Flow:** +1. **Authentication Check:** + - Verifies Admin role access + +2. **Form Fields:** + - ISBN (required, unique identifier) + - Title (required) + - Publisher selection from available publishers + - Publication year + - Price (required, numeric validation) + - Category + - Initial stock quantity + - Stock threshold for reordering + - Author assignment (multi-select) + +3. **Author Management:** + - Load all available authors + - Multi-select checkbox interface + - Create new authors if needed + +4. **Validation:** + - Required field validation + - ISBN format and uniqueness checking + - Price and stock numeric validation + - Author selection requirement + +5. **Creation Process:** + - Validates all form data + - Submits book creation request + - Handles success/error responses + - Redirects to book management on success + +--- + +### 14. Publisher Orders Page (`/admin/publisher-orders`) +**File:** [frontend/app/admin/publisher-orders/page.tsx](../frontend/app/admin/publisher-orders/page.tsx) + +**Purpose:** Manage replenishment orders for low-stock books + +**Logic Flow:** +1. **Order Data Loading:** + - Fetches all pending replenishment orders + - Loads book details for each order + - Combines order data with book information + +2. **Order Display:** + - Shows order ID, book details, current stock + - Displays order quantity and urgency indicators + - Book cover thumbnails with order information + +3. **Order Management:** + - **Confirm Order:** + - Button to confirm replenishment order + - Updates stock levels automatically + - Marks order as completed + - Shows confirmation loading state + + - **Order Status:** + - Pending orders highlighted + - Completed orders marked with checkmarks + - Time-based priority indicators + +4. **Book Information:** + - Shows current stock vs. threshold levels + - Displays book title, ISBN, category + - Indicates critical stock levels + +5. **Automated System:** + - Orders automatically generated when stock falls below threshold + - Admin confirmation required before processing + - Integrates with inventory management system + +--- + +### 15. Reports Page (`/admin/reports`) +**File:** [frontend/app/admin/reports/page.tsx](../frontend/app/admin/reports/page.tsx) + +**Purpose:** Business intelligence and analytics dashboard + +**Logic Flow:** +1. **Report Categories:** + - **Sales Reports:** + - Previous month total sales + - Sales by specific date + - Date range analysis + + - **Top Customers:** + - Customers by total purchase amount + - Customer ranking and statistics + - Purchase history insights + + - **Best-Selling Books:** + - Books by total sales volume + - Revenue by book category + - Inventory performance metrics + +2. **Sales Analytics:** + - **Previous Month Sales:** + - Automatic calculation of previous month revenue + - Comparison metrics and trends + + - **Date-Specific Sales:** + - Date picker for specific day analysis + - Real-time sales data fetching + - Daily performance metrics + +3. **Customer Analytics:** + - Top customer identification by spend + - Customer loyalty metrics + - Purchase pattern analysis + +4. **Book Performance:** + - Best-selling books ranking + - Revenue contribution by title + - Stock turnover analysis + +5. **Visual Representation:** + - Card-based layout for each report type + - Statistical indicators and trends + - Color-coded performance metrics + +6. **Interactive Features:** + - Date selection for custom reports + - Real-time data updates + - Export capabilities for business analysis + +--- + +### 16. Customer Management Page (`/admin/customers`) +**File:** [frontend/app/admin/customers/page.tsx](../frontend/app/admin/customers/page.tsx) + +**Purpose:** Admin interface for managing customer accounts + +**Logic Flow:** +1. **Customer Data Loading:** + - Fetches all registered customers + - Displays customer profiles and account information + +2. **Customer Information Display:** + - Customer details (name, email, registration date) + - Order history summary + - Account status and activity + +3. **Customer Management:** + - View detailed customer profiles + - Monitor customer activity and purchases + - Account management capabilities + +--- + +### 17. Admin Registration Page (`/admin/register-admin`) +**File:** [frontend/app/admin/register-admin/page.tsx](../frontend/app/admin/register-admin/page.tsx) + +**Purpose:** Create new administrator accounts + +**Logic Flow:** +1. **Registration Form:** + - Admin-specific account creation + - Required fields for admin users + - Role assignment and permissions + +2. **Validation:** + - Admin credential validation + - Security requirements for admin accounts + - Authorization checks + +3. **Account Creation:** + - Creates new admin user accounts + - Assigns appropriate admin permissions + - Integrates with authentication system --- @@ -725,11 +952,15 @@ - Type-safe API calls with TypeScript - Error handling and response parsing - Endpoints for: - - Books (getAll, getByIsbn) + - Books (getAll, getByIsbn, create, update, delete) - Cart (getCart, addItem, removeItem, checkout) - Orders (getAll) - Credit Cards (getAll, addCard, deleteCard) - - Users (profile management) + - Users (profile management, getAllCustomers) + - Authors (getAll, create, update) + - Publishers (getAll) + - Reports (totalSales, topCustomers, topBooks) + - Replenishment Orders (getAll, confirm) --- @@ -756,6 +987,8 @@ graph TD AddBook[Add New Book /admin/books/new] PublisherOrders[Publisher Orders /admin/publisher-orders] Reports[Reports /admin/reports] + Customers[Customer Management /admin/customers] + RegisterAdmin[Register Admin /admin/register-admin] %% Authentication Flow Signup --> Login @@ -776,6 +1009,9 @@ graph TD AdminHome --> AddBook AdminHome --> PublisherOrders AdminHome --> Reports + AdminHome --> Customers + AdminHome --> RegisterAdmin + ManageBooks --> AddBook %% Styling classDef authClass fill:#e1f5ff,stroke:#01579b,stroke-width:2px,color:#01579b @@ -784,7 +1020,7 @@ graph TD class Login,Signup authClass class UserHome,Books,BookDetails,SearchResults,Cart,Checkout,Orders,Profile customerClass - class AdminHome,ManageBooks,AddBook,PublisherOrders,Reports adminClass + class AdminHome,ManageBooks,AddBook,PublisherOrders,Reports,Customers,RegisterAdmin adminClass ``` --- @@ -838,4 +1074,12 @@ This Order Processing System implements a comprehensive e-commerce interface wit - **Comprehensive error handling** for better user experience - **Type safety** with TypeScript throughout -The customer journey flows naturally from browsing to purchasing, while admin screens provide centralized access to management operations. All screens maintain consistent authentication checks, loading states, and error handling patterns. +The customer journey flows naturally from browsing to purchasing, while admin screens provide comprehensive management capabilities including: + +- **Complete Book Management:** CRUD operations for books with real-time stock tracking +- **Inventory Control:** Automated replenishment orders with admin confirmation workflow +- **Business Analytics:** Comprehensive reports for sales, customers, and book performance +- **Customer Oversight:** Customer account management and activity monitoring +- **Admin User Management:** Secure admin account creation and role management + +All screens maintain consistent authentication checks, loading states, and error handling patterns with role-based access control ensuring proper security boundaries. diff --git a/docs/chen_er_diagram.svg b/docs/chen_er_diagram.svg new file mode 100644 index 0000000..ff8b5a5 --- /dev/null +++ b/docs/chen_er_diagram.svg @@ -0,0 +1,704 @@ + + + + + + +ER + + + +Publisher + +Publisher + + + +Publisher_publisher_id + + +publisher_id + + + + +Publisher--Publisher_publisher_id + + + + +Publisher_publisher_name + +publisher_name + + + +Publisher--Publisher_publisher_name + + + + +Publisher_address + +address + + + +Publisher--Publisher_address + + + + +Publisher_phone + + +phone + + + +Publisher--Publisher_phone + + + + +Author + +Author + + + +Author_author_id + + +author_id + + + + +Author--Author_author_id + + + + +Author_author_name + +author_name + + + +Author--Author_author_name + + + + +Book + +Book + + + +Book_isbn + + +isbn + + + + +Book--Book_isbn + + + + +Book_title + +title + + + +Book--Book_title + + + + +Book_pub_year + +pub_year + + + +Book--Book_pub_year + + + + +Book_price + +price + + + +Book--Book_price + + + + +Book_category + +category + + + +Book--Book_category + + + + +Book_stock + +stock + + + +Book--Book_stock + + + + +Book_threshold + +threshold + + + +Book--Book_threshold + + + + +User + +User + + + +User_u_id + + +u_id + + + + +User--User_u_id + + + + +User_username + +username + + + +User--User_username + + + + +User_password + +password + + + +User--User_password + + + + +User_last_name + +last_name + + + +User--User_last_name + + + + +User_first_name + +first_name + + + +User--User_first_name + + + + +User_email + +email + + + +User--User_email + + + + +User_phone + +phone + + + +User--User_phone + + + + +User_address + +address + + + +User--User_address + + + + +User_role + +role + + + +User--User_role + + + + +ReplenishmentOrder + +ReplenishmentOrder + + + +ReplenishmentOrder_order_id + + +order_id + + + + +ReplenishmentOrder--ReplenishmentOrder_order_id + + + + +ReplenishmentOrder_order_date + +order_date + + + +ReplenishmentOrder--ReplenishmentOrder_order_date + + + + +ReplenishmentOrder_quantity + +quantity + + + +ReplenishmentOrder--ReplenishmentOrder_quantity + + + + +ReplenishmentOrder_status + +status + + + +ReplenishmentOrder--ReplenishmentOrder_status + + + + +CustomerOrder + +CustomerOrder + + + +CustomerOrder_order_id + + +order_id + + + + +CustomerOrder--CustomerOrder_order_id + + + + +CustomerOrder_order_date + +order_date + + + +CustomerOrder--CustomerOrder_order_date + + + + +CustomerOrder_total_price + +total_price + + + +CustomerOrder--CustomerOrder_total_price + + + + +Cart + +Cart + + + +Cart_cart_id + + +cart_id + + + + +Cart--Cart_cart_id + + + + +CreditCard + +CreditCard + + + +CreditCard_card_id + + +card_id + + + + +CreditCard--CreditCard_card_id + + + + +CreditCard_u_id + +u_id + + + +CreditCard--CreditCard_u_id + + + + +CreditCard_cardholder_name + +cardholder_name + + + +CreditCard--CreditCard_cardholder_name + + + + +CreditCard_expiration_date + +expiration_date + + + +CreditCard--CreditCard_expiration_date + + + + +CreditCard_encrypted_card_number + +encrypted_card_number + + + +CreditCard--CreditCard_encrypted_card_number + + + + +CreditCard_last4 + +last4 + + + +CreditCard--CreditCard_last4 + + + + +CreditCard_keyver + +keyver + + + +CreditCard--CreditCard_keyver + + + + +CartItem + + +CartItem + + + +CartItem_quantity + +quantity + + + +CartItem--CartItem_quantity + + + + +CustomerOrderItem + + +CustomerOrderItem + + + +CustomerOrderItem_quantity + +quantity + + + +CustomerOrderItem--CustomerOrderItem_quantity + + + + +CustomerOrderItem_price + +price + + + +CustomerOrderItem--CustomerOrderItem_price + + + + +Published_By + +Published_By + + + +Published_By--Publisher + +1 + + + +Published_By--Book + +N + + + +Written_By + +Written_By + + + +Written_By--Author + +N + + + +Written_By--Book + +N + + + +Places + +Places + + + +Places--User + +1 + + + +Places--CustomerOrder + +N + + + +Replenishes + +Replenishes + + + +Replenishes--Book + +1 + + + +Replenishes--ReplenishmentOrder + +N + + + +Contains_Cart + + +Contains_Cart + + + +Contains_Cart--Book + +N + + + +Contains_Cart--Cart + +1 + + + +Contains_Cart--CartItem + +1 + + + +Contains_Cart--CartItem + +1 + + + +Contains_Order + + +Contains_Order + + + +Contains_Order--Book + +N + + + +Contains_Order--CustomerOrder + +1 + + + +Contains_Order--CustomerOrderItem + +1 + + + +Contains_Order--CustomerOrderItem + +1 + + + +Has_Cart + + +Has_Cart + + + +Has_Cart--User + +1 + + + +Has_Cart--Cart + +1 + + + +Has_CreditCard + + +Has_CreditCard + + + +Has_CreditCard--User + +N + + + +Has_CreditCard--CreditCard + +1 + + + diff --git a/frontend/app/user/profile/page.tsx b/frontend/app/user/profile/page.tsx index 92daf1d..65dcc20 100644 --- a/frontend/app/user/profile/page.tsx +++ b/frontend/app/user/profile/page.tsx @@ -233,29 +233,24 @@ export default function ProfilePage() { try { const currentUser = getCurrentUser(); if (!currentUser) return; - - // Note: This assumes there's an update endpoint. If not, we'll need to create one. - // For now, we'll show a message that profile updates need backend support - setError("Profile update functionality requires backend API endpoint. Please contact support."); - // Uncomment when backend endpoint is available: - // const response = await apiRequest(`/users/${currentUser.username}`, { - // method: "PUT", - // body: JSON.stringify({ - // FirstName: formData.firstName, - // LastName: formData.lastName, - // Email: formData.email, - // Phone: formData.phone || null, - // Address: formData.address || null, - // }), - // }); + const response = await apiRequest(`/users/me`, { + method: "PUT", + body: JSON.stringify({ + FirstName: formData.firstName, + LastName: formData.lastName, + Email: formData.email, + Phone: formData.phone || null, + Address: formData.address || null, + }), + }); - // if (response.error) { - // setError(response.error); - // } else { - // setSuccess("Profile updated successfully!"); - // await loadProfile(); - // } + if (response.error) { + setError(response.error); + } else { + setSuccess("Profile updated successfully!"); + await loadProfile(); + } } catch (error: any) { setError("Failed to update profile: " + (error.message || "Unknown error")); } finally {