From 4b630ef5dd5e9dba08e5da349ef06eace49df11b Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Sun, 29 Jun 2025 14:26:22 +0330 Subject: [PATCH 1/7] Update Session08 Backend.md added some parts of profile --- .../Session08/Session08 Backend.md | 68 ++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index 13027e1..6aa0346 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -190,11 +190,73 @@ You can also find another version of this DTO in [Here](https://github.com/Mehrd - [ ] Implement: - [ ] Edit Email (with validation): `EditEmailDto`, service method, and controller endpoint. + ``` + public class EditEmailDto + { + [EmailAddress(ErrorMessage = "Invalid email address format")] + public string NewEmail { get; set; } + } + ``` - [ ] Edit Password: `EditPasswordDto`, service and controller. - - [ ] Edit Person Info: `UpsertAccountPersonDto`, and endpoint to upsert personal data. - - [ ] Edit BankAccountDetail: `UpsertBankAccountDetailDto` and relevant logic. + ``` + public class EditPasswordDto + { + [Required(ErrorMessage = "Old password is required")] + public string OldPassword { get; set; } -- [ ] Add mapping for all Dtos and fix related properties like `CreatorAccountId`. + [Required(ErrorMessage = "New password is required")] + [MinLength(8, ErrorMessage = "At least 8 chars")] + public string NewPassword { get; set; } + + [Compare("Password", ErrorMessage = "Password doesn't match")] + public string ConfirmNewPassword { get; set; } + } + ``` + - [ ] Edit Person Info: `UpsertPersonDto`, and endpoint to upsert personal data. + ``` + public class UpsertPersonDto + { + public long Id { get; set; } + public long CreatorId { get; set; } + + [Required(ErrorMessage = "Firstname is required")] + public string FirstName { get; set; } + + [Required(ErrorMessage = "Lastname is required")] + public string LastName { get; set; } + + [Required(ErrorMessage = "National Id number is required")] + [RegularExpression(@"^\d{10}$", ErrorMessage = "National ID number must be exactly 10 digits")] + public string IdNumber { get; set; } + + [Required(ErrorMessage = "Gender is required")] + public short GenderId { get; set; } + + [Required(ErrorMessage = "Phone number is required")] + public string PhoneNumber { get; set; } + + [Required(ErrorMessage = "Birth date is required")] + public DateTime BirthDate { get; set; } + } + ``` + - [ ] Edit BankAccountDetail: `UpsertBankAccountDetailDto` and relevant logic. + ``` + public class UpsertBankAccountDto + { + [MinLength(24)] + [MaxLength(24)] + public string? IBAN { get; set; } + + [MinLength(16)] + [MaxLength(16)] + public string? CardNumber { get; set; } + + [MinLength(8)] + public string? BankAccountNumber { get; set; } + } + ``` + +- [ ] Add mapping for all Dtos and check related properties like `CreatorAccountId`. ## ✅ List of Travelers From be6110210fca678ed5b2c49ca55e950cebc2089a Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:43:09 +0330 Subject: [PATCH 2/7] Update Session08 Backend.md fixed UpsertAccountPersonDto --- ProjectOrientedSessions/Session08/Session08 Backend.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index 6aa0346..fb97b37 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -212,9 +212,9 @@ You can also find another version of this DTO in [Here](https://github.com/Mehrd public string ConfirmNewPassword { get; set; } } ``` - - [ ] Edit Person Info: `UpsertPersonDto`, and endpoint to upsert personal data. + - [ ] Edit Person Info: `UpsertAccountPersonDto`, and endpoint to upsert personal data. ``` - public class UpsertPersonDto + public class UpsertAccountPersonDto { public long Id { get; set; } public long CreatorId { get; set; } From 1e65e56f53424fff6ed0c7b41d49a02e993b83ed Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:17:17 +0330 Subject: [PATCH 3/7] Update Session08 Backend.md upsert person + some minor changes --- .../Session08/Session08 Backend.md | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index fb97b37..6321118 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -64,7 +64,7 @@ builder.Services.AddScoped(); public class ProfileDto { // from account - public string PhoneNumber { get; set; } + public string AccountPhoneNumber { get; set; } public string Email { get; set; } public decimal Balance { get; set; } @@ -72,6 +72,7 @@ public class ProfileDto public string FirstName { get; set; } public string LastName { get; set; } public string IdNumber { get; set; } + public string PersonPhoneNumber { get; set; } public DateTime? BirthDate { get; set; } // from bank-account @@ -212,9 +213,9 @@ You can also find another version of this DTO in [Here](https://github.com/Mehrd public string ConfirmNewPassword { get; set; } } ``` - - [ ] Edit Person Info: `UpsertAccountPersonDto`, and endpoint to upsert personal data. + - [ ] Edit Person Info: `PersonDto`, and endpoint to upsert personal data. ``` - public class UpsertAccountPersonDto + public class PersonDto { public long Id { get; set; } public long CreatorId { get; set; } @@ -261,9 +262,78 @@ You can also find another version of this DTO in [Here](https://github.com/Mehrd ## ✅ List of Travelers -- [ ] Add `GetPeople` endpoint in `AccountController`. -- [ ] Implement separation of `UpsertAccountPerson` and `UpsertPerson`. -- [ ] Adjust Dto: `PersonDto` (with `id`, `creatorAccountId`, `englishFirstName`, etc.). +- [ ] Add `GetMyPeople` endpoint in `AccountController`. To do so, first add essential methods in `PersonRepository`, `AccountService` and related interfaces. + +- [ ] Implement `UpsertAccountPerson` and `UpsertPerson`. Note that they should be considered separated. +``` +public async Task> UpsertAccountPersonAsync(long accountId, PersonDto dto) +{ + var account = await _accountRepository.GetByIdAsync(accountId); + if (account == null) + { + throw new Exception("Account not found"); + } + + // if account is not null, update its person + Person person; + if (account.PersonId.HasValue) + { + person = await _personRepository.GetByIdAsync(account.PersonId.Value); + if (person == null) + { + return Result.Error(0, "No person found for this account"); + } + + _mapper.Map(dto, person); + person.CreatorId = account.Id; + person.Id = account.PersonId.Value; + _personRepository.Update(person); + } + else + { + person = _mapper.Map(dto); + person.CreatorId = account.Id; + await _personRepository.InsertAsync(person); + } + await _unitOfWork.CompleteAsync(); + + account.PersonId = person.Id; + _accountRepository.Update(account); + await _unitOfWork.CompleteAsync(); + + return Result.Success(person.Id); +} + +public async Task> UpsertPersonAsync(long accountId, PersonDto dto) +{ + var account = await _accountRepository.GetByIdAsync(accountId); + if (account == null) + { + throw new Exception("Account not found"); + } + + Person person = (await _personRepository.FindAsync(p => p.IdNumber == dto.IdNumber && p.CreatorId == accountId)).FirstOrDefault(); + if (person != null) + { + if (dto.Id > 0 && dto.Id != person.Id) + { + return Result.Error(0, "A person with this id number exists"); + } + _mapper.Map(dto, person); + person.CreatorId = accountId; + _personRepository.Update(person); + } + else + { + person = _mapper.Map(dto); + person.CreatorId = accountId; + await _personRepository.InsertAsync(person); + } + await _unitOfWork.CompleteAsync(); + + return Result.Success(person.Id); +} +``` ## ✅ My Travels Tab From 89027634384e68e3b85b95c30e6be36cf36434ed Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:27:03 +0330 Subject: [PATCH 4/7] Update Session08 Backend.md upsert person endpoints --- .../Session08/Session08 Backend.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index 6321118..c882a9f 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -334,6 +334,47 @@ public async Task> UpsertPersonAsync(long accountId, PersonDto dto) return Result.Success(person.Id); } ``` +``` +[HttpPost("account-person")] +public async Task UpsertAccountPerson([FromBody] PersonDto dto) +{ + long accountId = _userContext.GetUserId(); + if (accountId <= 0) + { + return Unauthorized(); + } + + var result = await _personService.UpsertAccountPersonAsync(accountId, dto); + return result.Status switch + { + ResultStatus.Success => NoContent(), + ResultStatus.Unauthorized => Unauthorized(result.ErrorMessage), + ResultStatus.NotFound => NotFound(result.ErrorMessage), + ResultStatus.ValidationError => BadRequest(result.ErrorMessage), + _ => StatusCode(500, result.ErrorMessage) + }; +} + +[HttpPost("person")] +public async Task UpsertPerson([FromBody] PersonDto dto) +{ + long accountId = _userContext.GetUserId(); + if (accountId <= 0) + { + return Unauthorized(); + } + + var result = await _personService.UpsertPersonAsync(accountId, dto); + return result.Status switch + { + ResultStatus.Success => NoContent(), + ResultStatus.Unauthorized => Unauthorized(result.ErrorMessage), + ResultStatus.NotFound => NotFound(result.ErrorMessage), + ResultStatus.ValidationError => BadRequest(result.ErrorMessage), + _ => StatusCode(500, result.ErrorMessage) + }; +} +``` ## ✅ My Travels Tab From ffaff64666690ee96c6b860b39050d54f512fdf8 Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:23:10 +0330 Subject: [PATCH 5/7] Update Session08 Backend.md updated the part about getting travels --- .../Session08/Session08 Backend.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index c882a9f..9416d69 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -380,8 +380,77 @@ public async Task UpsertPerson([FromBody] PersonDto dto) ## ✅ My Travels Tab - [ ] Create `TicketOrderSummaryDto` (includes cities, vehicle name, price, etc.). +``` +public class TicketOrderSummaryDto +{ + public long Id { get; set; } + public string SerialNumber { get; set; } + public DateTime BoughtAt { get; set; } + + // transaction + public decimal Price { get; set; } + + // transportation + public DateTime TravelStartDate { get; set; } + public DateTime? TravelEndDate { get; set; } + + // city + public string FromCity { get; set; } + public string ToCity { get; set; } + + // company + public string CompanyName { get; set; } + + // vehicle data + public short VehicleTypeId { get; set; } + public string VehicleName { get; set; } +} +``` + - [ ] Add `GetTravels` in `AccountService`, and expose `GetMyTravels` in controller. +First, update interfaces and TicketOrderRepository, add the **mappings** and then go for the other things + +In AccountService: +``` +public async Task>> GetTravelsAsync(long accountId) +{ + var result = await _ticketOrderRepository.GetAllByBuyerId(accountId); + if (result == null) + { + return Result>.NotFound(null); + } + + return Result>.Success(_mapper.Map>(result)); +} +``` + +In AccountController: +``` +[HttpGet("my-travels")] +public async Task GetMyTravels() +{ + long buyerId = _userContext.GetUserId(); + if (buyerId <= 0) + { + return Unauthorized(); + } + + var result = await _accountService.GetTravelsAsync(buyerId); + if (result.IsSuccess) + { + return Ok(result.Data); + } + + return result.Status switch + { + ResultStatus.Unauthorized => Unauthorized(result.ErrorMessage), + ResultStatus.NotFound => NotFound(result.ErrorMessage), + ResultStatus.ValidationError => BadRequest(result.ErrorMessage), + _ => StatusCode(500, result.ErrorMessage) + }; +} +``` ## ✅ My Transactions Tab From 03bbe106b0d342a51381131e30ad33f6785af5d3 Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:46:24 +0330 Subject: [PATCH 6/7] Update Session08 Backend.md final part done --- .../Session08/Session08 Backend.md | 90 ++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/ProjectOrientedSessions/Session08/Session08 Backend.md b/ProjectOrientedSessions/Session08/Session08 Backend.md index 9416d69..1a24b04 100644 --- a/ProjectOrientedSessions/Session08/Session08 Backend.md +++ b/ProjectOrientedSessions/Session08/Session08 Backend.md @@ -455,7 +455,91 @@ public async Task GetMyTravels() ## ✅ My Transactions Tab - [ ] Create `TransactionDto` and mapping. -- [ ] Add method to get transactions by `AccountId`. -- [ ] Expose `GetMyTransactions` in `AccountController`. +``` +public class TransactionDto +{ + public long Id { get; set; } + public short TransactionTypeId { get; set; } + public long AccountId { get; set; } + public long? TicketOrderId { get; set; } + public decimal BaseAmount { get; set; } + public decimal FinalAmount { get; set; } + public required string SerialNumber { get; set; } + public DateTime CreatedAt { get; set; } + public string? Description { get; set; } + public string TransactionType { get; set; } +} +``` + +- [ ] Add method to get transactions by `AccountId` in `TransactionRepository`. +``` +public async Task> GetTransactionsByAccountIdAsync(long accountId) +{ + var transactions = await DbSet + .Include(t => t.TransactionType) + .Include(t => t.TicketOrder) + .Where(t => t.AccountId == accountId).ToListAsync(); + return transactions; +} +``` + +- [ ] Expose `GetMyTransactions` in `AccountController`. It's obvious you should first add the essential method `GetTransactionsAsync` in `AccountService`. +``` +[HttpGet("my-transactions")] +public async Task GetMyTransactions() +{ + long accountId = _userContext.GetUserId(); + if (accountId <= 0) + { + return Unauthorized(); + } + + var result = await _accountService.GetTransactionsAsync(accountId); + if (result.IsSuccess) + { + return Ok(result.Data); + } + + return result.Status switch + { + ResultStatus.Unauthorized => Unauthorized(result.ErrorMessage), + ResultStatus.NotFound => NotFound(result.ErrorMessage), + ResultStatus.ValidationError => BadRequest(result.ErrorMessage), + _ => StatusCode(500, result.ErrorMessage) + }; +} +``` - [ ] Add modal to simulate balance top-up (manual input). -- [ ] Format amount text based on transaction type: green (+) for income, red (–) for expense. +``` +public class TopUpDto +{ + public decimal Amount { get; set; } +} +``` +- [ ] Add `TransactionService` and use its method `CreateTopUpAsync` to create and add a new transaction in `AccountService`. Then add an endpoint just like before. +``` +public async Task> TopUpAsync(long accountId, TopUpDto dto) +{ + var account = await _accountRepository.GetByIdAsync(accountId); + if (account == null) + { + return Result.Error(0, "Account not found"); + } + + account.Deposit(dto.Amount); + _accountRepository.Update(account); + await _unitOfWork.CompleteAsync(); + + var transactionId = await _transactionService.CreateTopUpAsync(accountId, dto.Amount); + return Result.Success(transactionId.Data); +} +``` + +## Postman +Considering that all endpoints in `AccountController` require Authorization, You need to test your api in **Postman**. + +
+ + +Postman is a client which lets the user test api professionally. +You can download it in [this link](https://www.postman.com/downloads/) and get started with it using [this video](https://www.youtube.com/watch?v=wEOLZq-7DYs&pp=0gcJCfwAo7VqN5tD) From 8e95a03d761148b094215848458bb1d67f1ec5f8 Mon Sep 17 00:00:00 2001 From: Amin <69254513+AminGh05@users.noreply.github.com> Date: Fri, 4 Jul 2025 00:32:39 +0330 Subject: [PATCH 7/7] Update Session08 Frontend.md updated the first parts --- .../Session08/Session08 Frontend.md | 83 ++++++++++++++----- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/ProjectOrientedSessions/Session08/Session08 Frontend.md b/ProjectOrientedSessions/Session08/Session08 Frontend.md index 001b37e..5e4505d 100644 --- a/ProjectOrientedSessions/Session08/Session08 Frontend.md +++ b/ProjectOrientedSessions/Session08/Session08 Frontend.md @@ -1,17 +1,56 @@ -## ✅ Frontend Profile Page Implementation Checklist +# ✅ Frontend Profile Page Implementation Checklist -### ⚙️ Tooling & Fixes +## ⚙️ Tooling & Fixes - [ ] Install and configure `react-hook-form` ---- -### 🔐 Account & Authentication -- [ ] Use Axios request interceptor to: - - [ ] Attach token to requests - - [ ] Handle logout logic ---- -### API -- [ ] Provide a method for each new endpoint implemented in backend ---- -### 🗂️ Additional Profile Tabs (initial setup) +``` +npm install react-hook-form +``` + +## 🔐 Account & Authentication +- [ ] Use Axios request interceptor to attach token to requests and handle logout logic +``` +// add a request interceptor to include JWT token if available +agent.interceptors.request.use((config) => { + const token = useAuthStore.getState().token; + if (token) { + config.headers = config.headers || {}; + config.headers["Authorization"] = `Bearer ${token}`; + } + return config; +}); + +// add a response interceptor to handle 401 Unauthorized +agent.interceptors.response.use( + (res) => res, + (error) => { + if (error.response?.status === 401) { + useAuthStore.getState().logout(); + window.location.href = "/login"; // redirect to login + } + return Promise.reject(error); + } +); +``` + +## API +- [ ] Provide a method for each new endpoint implemented in backend +``` +const Profile = { + getProfile: () => request.get('/account/profile'), + editEmail: (data: EditEmailDto) => request.put('/account/email', data), + editPassword: (data: EditPasswordDto) => request.put('/account/password', data), + upsertAccountPerson: (data: PersonDto) => request.post('/account/account-person', data), + upsertPerson: (data: PersonDto) => request.post('/account/person', data), + upsertBankDetail: (data: UpsertBankAccountDetailDto) => request.post('/account/bank-detail', data), + getMyPeople: () => request.get('/account/my-people'), + getMyTravels: () => request.get('/account/my-travels'), + getMyTransactions: () => request.get('/account/my-transactions'), + topUp: (data : topUpDto) => request.post('/account/top-up', data) +}; + +``` + +## 🗂️ Additional Profile Tabs (initial setup) - [ ] Add empty pages/tabs for: - [ ] `ProfileSummary` @@ -25,28 +64,26 @@ - [ ] Define and adjust routes for profile and its tabs - [ ] Implement route-based tab handling inside `ProfilePage` ---- -### Profile Summary -#### 📦 DTOs / Models +## Profile Summary +### 📦 DTOs / Models - Add models for: - [ ] `EditEmailDto` - [ ] `EditPasswordDto` - [ ] `PersonDto` - [ ] `ProfileDto` - [ ] `UpsertBankAccountDetailDto` + - [ ] Implement the process of showing and editing the data ---- -### 🧍 List of Travelers + +## 🧍 List of Travelers - [ ] Implement `ListOfTravelers` page for showing, editing, and adding new people ---- -### 💳 Transactions Module + +## 💳 Transactions Module - [ ] Add `TransactionDto` model - [ ] Implement `MyTransactions` page ---- -### 🚆 Travel Module + +## 🚆 Travel Module - [ ] Add `TicketOrderSummaryDto`, `TravelerTicketDto` models - [ ] Implement `MyTravels` page - [ ] Implement `TravelOrderDetailsPage` - ----