vault backup: 2025-07-06 20:36:58
This commit is contained in:
@@ -64,7 +64,7 @@ builder.Services.AddScoped<IUserContext, UserContext>();
|
|||||||
public class ProfileDto
|
public class ProfileDto
|
||||||
{
|
{
|
||||||
// from account
|
// from account
|
||||||
public string PhoneNumber { get; set; }
|
public string AccountPhoneNumber { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
public decimal Balance { get; set; }
|
public decimal Balance { get; set; }
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ public class ProfileDto
|
|||||||
public string FirstName { get; set; }
|
public string FirstName { get; set; }
|
||||||
public string LastName { get; set; }
|
public string LastName { get; set; }
|
||||||
public string IdNumber { get; set; }
|
public string IdNumber { get; set; }
|
||||||
|
public string PersonPhoneNumber { get; set; }
|
||||||
public DateTime? BirthDate { get; set; }
|
public DateTime? BirthDate { get; set; }
|
||||||
|
|
||||||
// from bank-account
|
// from bank-account
|
||||||
@@ -190,30 +191,355 @@ You can also find another version of this DTO in [Here](https://github.com/Mehrd
|
|||||||
|
|
||||||
- [ ] Implement:
|
- [ ] Implement:
|
||||||
- [ ] Edit Email (with validation): `EditEmailDto`, service method, and controller endpoint.
|
- [ ] 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 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: `PersonDto`, and endpoint to upsert personal data.
|
||||||
|
```
|
||||||
|
public class PersonDto
|
||||||
|
{
|
||||||
|
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
|
## ✅ List of Travelers
|
||||||
|
|
||||||
- [ ] Add `GetPeople` endpoint in `AccountController`.
|
- [ ] Add `GetMyPeople` endpoint in `AccountController`. To do so, first add essential methods in `PersonRepository`, `AccountService` and related interfaces.
|
||||||
- [ ] Implement separation of `UpsertAccountPerson` and `UpsertPerson`.
|
|
||||||
- [ ] Adjust Dto: `PersonDto` (with `id`, `creatorAccountId`, `englishFirstName`, etc.).
|
- [ ] Implement `UpsertAccountPerson` and `UpsertPerson`. Note that they should be considered separated.
|
||||||
|
```
|
||||||
|
public async Task<Result<long>> 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<long>.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<Person>(dto);
|
||||||
|
person.CreatorId = account.Id;
|
||||||
|
await _personRepository.InsertAsync(person);
|
||||||
|
}
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
account.PersonId = person.Id;
|
||||||
|
_accountRepository.Update(account);
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
return Result<long>.Success(person.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<long>> 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<long>.Error(0, "A person with this id number exists");
|
||||||
|
}
|
||||||
|
_mapper.Map(dto, person);
|
||||||
|
person.CreatorId = accountId;
|
||||||
|
_personRepository.Update(person);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
person = _mapper.Map<Person>(dto);
|
||||||
|
person.CreatorId = accountId;
|
||||||
|
await _personRepository.InsertAsync(person);
|
||||||
|
}
|
||||||
|
await _unitOfWork.CompleteAsync();
|
||||||
|
|
||||||
|
return Result<long>.Success(person.Id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
[HttpPost("account-person")]
|
||||||
|
public async Task<IActionResult> 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<IActionResult> 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
|
## ✅ My Travels Tab
|
||||||
|
|
||||||
- [ ] Create `TicketOrderSummaryDto` (includes cities, vehicle name, price, etc.).
|
- [ ] 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.
|
- [ ] 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<Result<List<TicketOrderSummaryDto>>> GetTravelsAsync(long accountId)
|
||||||
|
{
|
||||||
|
var result = await _ticketOrderRepository.GetAllByBuyerId(accountId);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return Result<List<TicketOrderSummaryDto>>.NotFound(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result<List<TicketOrderSummaryDto>>.Success(_mapper.Map<List<TicketOrderSummaryDto>>(result));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In AccountController:
|
||||||
|
```
|
||||||
|
[HttpGet("my-travels")]
|
||||||
|
public async Task<IActionResult> 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
|
## ✅ My Transactions Tab
|
||||||
|
|
||||||
- [ ] Create `TransactionDto` and mapping.
|
- [ ] 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<List<Transaction>> 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<IActionResult> 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).
|
- [ ] 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<Result<long>> TopUpAsync(long accountId, TopUpDto dto)
|
||||||
|
{
|
||||||
|
var account = await _accountRepository.GetByIdAsync(accountId);
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
return Result<long>.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<long>.Success(transactionId.Data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Postman
|
||||||
|
Considering that all endpoints in `AccountController` require Authorization, You need to test your api in **Postman**.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c2/Postman_%28software%29.png" width="100%">
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -1,17 +1,56 @@
|
|||||||
## ✅ Frontend Profile Page Implementation Checklist
|
# ✅ Frontend Profile Page Implementation Checklist
|
||||||
|
|
||||||
### ⚙️ Tooling & Fixes
|
## ⚙️ Tooling & Fixes
|
||||||
- [ ] Install and configure `react-hook-form`
|
- [ ] Install and configure `react-hook-form`
|
||||||
---
|
```
|
||||||
### 🔐 Account & Authentication
|
npm install react-hook-form
|
||||||
- [ ] Use Axios request interceptor to:
|
```
|
||||||
- [ ] Attach token to requests
|
|
||||||
- [ ] Handle logout logic
|
## 🔐 Account & Authentication
|
||||||
---
|
- [ ] Use Axios request interceptor to attach token to requests and handle logout logic
|
||||||
### API
|
```ts
|
||||||
- [ ] Provide a method for each new endpoint implemented in backend
|
// add a request interceptor to include JWT token if available
|
||||||
---
|
agent.interceptors.request.use((config) => {
|
||||||
### 🗂️ Additional Profile Tabs (initial setup)
|
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
|
||||||
|
```ts
|
||||||
|
const Profile = {
|
||||||
|
getProfile: () => request.get<ProfileDto>('/account/profile'),
|
||||||
|
editEmail: (data: EditEmailDto) => request.put<void>('/account/email', data),
|
||||||
|
editPassword: (data: EditPasswordDto) => request.put<void>('/account/password', data),
|
||||||
|
upsertAccountPerson: (data: PersonDto) => request.post<number>('/account/account-person', data),
|
||||||
|
upsertPerson: (data: PersonDto) => request.post<number>('/account/person', data),
|
||||||
|
upsertBankDetail: (data: UpsertBankAccountDetailDto) => request.post<void>('/account/bank-detail', data),
|
||||||
|
getMyPeople: () => request.get<PersonDto[]>('/account/my-people'),
|
||||||
|
getMyTravels: () => request.get<TicketOrderSummaryDto[]>('/account/my-travels'),
|
||||||
|
getMyTransactions: () => request.get<TransactionDto[]>('/account/my-transactions'),
|
||||||
|
topUp: (data : topUpDto) => request.post<number>('/account/top-up', data)
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🗂️ Additional Profile Tabs (initial setup)
|
||||||
|
|
||||||
- [ ] Add empty pages/tabs for:
|
- [ ] Add empty pages/tabs for:
|
||||||
- [ ] `ProfileSummary`
|
- [ ] `ProfileSummary`
|
||||||
@@ -25,28 +64,26 @@
|
|||||||
- [ ] Define and adjust routes for profile and its tabs
|
- [ ] Define and adjust routes for profile and its tabs
|
||||||
- [ ] Implement route-based tab handling inside `ProfilePage`
|
- [ ] Implement route-based tab handling inside `ProfilePage`
|
||||||
|
|
||||||
---
|
## Profile Summary
|
||||||
### Profile Summary
|
### 📦 DTOs / Models
|
||||||
#### 📦 DTOs / Models
|
|
||||||
- Add models for:
|
- Add models for:
|
||||||
- [ ] `EditEmailDto`
|
- [ ] `EditEmailDto`
|
||||||
- [ ] `EditPasswordDto`
|
- [ ] `EditPasswordDto`
|
||||||
- [ ] `PersonDto`
|
- [ ] `PersonDto`
|
||||||
- [ ] `ProfileDto`
|
- [ ] `ProfileDto`
|
||||||
- [ ] `UpsertBankAccountDetailDto`
|
- [ ] `UpsertBankAccountDetailDto`
|
||||||
|
|
||||||
- [ ] Implement the process of showing and editing the data
|
- [ ] 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
|
- [ ] Implement `ListOfTravelers` page for showing, editing, and adding new people
|
||||||
|
|
||||||
---
|
|
||||||
### 💳 Transactions Module
|
## 💳 Transactions Module
|
||||||
- [ ] Add `TransactionDto` model
|
- [ ] Add `TransactionDto` model
|
||||||
- [ ] Implement `MyTransactions` page
|
- [ ] Implement `MyTransactions` page
|
||||||
---
|
|
||||||
### 🚆 Travel Module
|
## 🚆 Travel Module
|
||||||
- [ ] Add `TicketOrderSummaryDto`, `TravelerTicketDto` models
|
- [ ] Add `TicketOrderSummaryDto`, `TravelerTicketDto` models
|
||||||
- [ ] Implement `MyTravels` page
|
- [ ] Implement `MyTravels` page
|
||||||
- [ ] Implement `TravelOrderDetailsPage`
|
- [ ] Implement `TravelOrderDetailsPage`
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
Reference in New Issue
Block a user