Yuyuqt commited on
Commit
1723ad3
·
1 Parent(s): c312181

add: some features

Browse files
Backend/Features/Borrowing/BorrowingController.cs CHANGED
@@ -34,6 +34,21 @@ namespace Backend.Features.Borrowings
34
  }
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  [HttpPost("return/{id}")]
38
  [Authorize(Roles = "Librarian")]
39
  public async Task<ActionResult<BorrowingDto>> ReturnBook(int id)
 
34
  }
35
  }
36
 
37
+ [HttpPost("return-request/{id}")]
38
+ [Authorize]
39
+ public async Task<ActionResult<BorrowingDto>> RequestReturn(int id)
40
+ {
41
+ try
42
+ {
43
+ var borrowing = await _borrowingService.RequestReturnAsync(id);
44
+ return Ok(borrowing);
45
+ }
46
+ catch (Exception ex)
47
+ {
48
+ return BadRequest(new { message = ex.Message });
49
+ }
50
+ }
51
+
52
  [HttpPost("return/{id}")]
53
  [Authorize(Roles = "Librarian")]
54
  public async Task<ActionResult<BorrowingDto>> ReturnBook(int id)
Backend/Features/Borrowing/BorrowingService.cs CHANGED
@@ -7,6 +7,7 @@ namespace Backend.Features.Borrowings
7
  public interface IBorrowingService
8
  {
9
  Task<BorrowingDto> BorrowBookAsync(int userId, int bookId);
 
10
  Task<BorrowingDto> ReturnBookAsync(int borrowingId);
11
  Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId);
12
  Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync();
@@ -107,6 +108,21 @@ namespace Backend.Features.Borrowings
107
  return MapToDto(borrowing);
108
  }
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  public async Task<BorrowingDto> ReturnBookAsync(int borrowingId)
111
  {
112
  var borrowing = await _context.Borrowings
 
7
  public interface IBorrowingService
8
  {
9
  Task<BorrowingDto> BorrowBookAsync(int userId, int bookId);
10
+ Task<BorrowingDto> RequestReturnAsync(int borrowingId);
11
  Task<BorrowingDto> ReturnBookAsync(int borrowingId);
12
  Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId);
13
  Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync();
 
108
  return MapToDto(borrowing);
109
  }
110
 
111
+ public async Task<BorrowingDto> RequestReturnAsync(int borrowingId)
112
+ {
113
+ var borrowing = await _context.Borrowings
114
+ .Include(b => b.Book)
115
+ .Include(b => b.User)
116
+ .FirstOrDefaultAsync(b => b.Id == borrowingId);
117
+
118
+ if (borrowing == null) throw new Exception("Borrowing record not found.");
119
+ if (borrowing.Status != "Borrowed") throw new Exception("Only borrowed books can be requested for return.");
120
+
121
+ borrowing.Status = "PendingReturn";
122
+ await _context.SaveChangesAsync();
123
+ return MapToDto(borrowing);
124
+ }
125
+
126
  public async Task<BorrowingDto> ReturnBookAsync(int borrowingId)
127
  {
128
  var borrowing = await _context.Borrowings
Frontend/Controllers/BorrowingsController.cs CHANGED
@@ -20,6 +20,28 @@ namespace Frontend.Controllers
20
  return View(borrowings);
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  [Authorize(Roles = "Librarian")]
24
  public async Task<IActionResult> Manage()
25
  {
 
20
  return View(borrowings);
21
  }
22
 
23
+ [HttpPost]
24
+ public async Task<IActionResult> RequestReturn(int id)
25
+ {
26
+ var success = await _apiClient.RequestReturnAsync(id);
27
+ if (success)
28
+ {
29
+ TempData["Success"] = "Return request sent! Awaiting librarian approval.";
30
+ }
31
+ else
32
+ {
33
+ TempData["Error"] = "Failed to request return.";
34
+ }
35
+ return RedirectToAction(nameof(Index));
36
+ }
37
+
38
+ [HttpGet]
39
+ public async Task<IActionResult> Rewards()
40
+ {
41
+ var rewards = await _apiClient.GetActiveRewardsAsync();
42
+ return View(rewards);
43
+ }
44
+
45
  [Authorize(Roles = "Librarian")]
46
  public async Task<IActionResult> Manage()
47
  {
Frontend/Models/Dtos/LibraryDtos.cs CHANGED
@@ -145,6 +145,27 @@ namespace Frontend.Models.Dtos
145
  public double LifetimePoints { get; set; }
146
  }
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  // User DTOs
149
  public class UserDto
150
  {
 
145
  public double LifetimePoints { get; set; }
146
  }
147
 
148
+ public class LoyaltyRewardDto
149
+ {
150
+ [JsonPropertyName("id")]
151
+ public string Id { get; set; } = string.Empty;
152
+
153
+ [JsonPropertyName("name")]
154
+ public string Name { get; set; } = string.Empty;
155
+
156
+ [JsonPropertyName("description")]
157
+ public string Description { get; set; } = string.Empty;
158
+
159
+ [JsonPropertyName("pointCost")]
160
+ public double PointCost { get; set; }
161
+
162
+ [JsonPropertyName("stockQuantity")]
163
+ public int StockQuantity { get; set; }
164
+
165
+ [JsonPropertyName("isActive")]
166
+ public bool IsActive { get; set; }
167
+ }
168
+
169
  // User DTOs
170
  public class UserDto
171
  {
Frontend/Services/LibraryApiClient.cs CHANGED
@@ -183,6 +183,27 @@ namespace Frontend.Services
183
  }
184
  }
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  #region Users
187
  public async Task<IEnumerable<UserDto>> GetUsersAsync()
188
  {
 
183
  }
184
  }
185
 
186
+ public async Task<bool> RequestReturnAsync(int borrowingId)
187
+ {
188
+ var response = await _httpClient.PostAsync($"api/borrowings/return-request/{borrowingId}", null);
189
+ return response.IsSuccessStatusCode;
190
+ }
191
+
192
+ public async Task<IEnumerable<LoyaltyRewardDto>> GetActiveRewardsAsync()
193
+ {
194
+ try {
195
+ // Fetch from the external loyalty API directly or via our backend proxy
196
+ // The user specified the URL: http://150.95.88.91:4100/api/v1/rewards/active/THS-LMS
197
+ // We'll use a new HttpClient or just the existing one if configured correctly.
198
+ // However, our backend doesn't have a proxy for this yet.
199
+ // For simplicity, let's assume we call it directly or via a backend endpoint we'll add.
200
+ // Better: Let's add a backend proxy in LoyaltyController to avoid CORS issues if this was a browser app.
201
+ // In ASP.NET Core MVC, the server-side HttpClient can call it directly.
202
+ var client = new HttpClient();
203
+ return await client.GetFromJsonAsync<IEnumerable<LoyaltyRewardDto>>("http://150.95.88.91:4100/api/v1/rewards/active/THS-LMS") ?? Enumerable.Empty<LoyaltyRewardDto>();
204
+ } catch { return Enumerable.Empty<LoyaltyRewardDto>(); }
205
+ }
206
+
207
  #region Users
208
  public async Task<IEnumerable<UserDto>> GetUsersAsync()
209
  {
Frontend/Views/Books/Details.cshtml CHANGED
@@ -46,25 +46,31 @@
46
  </div>
47
 
48
  <div class="flex items-center gap-4 pt-4">
49
- @if (Model.AvailableCopies > 0)
50
  {
51
- <form asp-controller="Borrowings" asp-action="Borrow" method="POST">
52
- <input type="hidden" name="BookId" value="@Model.Id" />
53
- <button type="submit" class="btn-primary py-3 px-8 text-base shadow-lg shadow-slate-200">Borrow This Book</button>
54
- </form>
 
 
 
 
 
 
 
55
  }
56
- else
 
57
  {
58
- <button disabled class="btn-primary py-3 px-8 text-base opacity-50 cursor-not-allowed">Out of Stock</button>
 
 
 
 
 
 
59
  }
60
-
61
- <a asp-action="Edit" asp-route-id="@Model.Id" class="btn-secondary py-3 px-8 text-base">Edit Details</a>
62
-
63
- <form asp-action="Delete" asp-route-id="@Model.Id" method="POST" onsubmit="return confirm('Are you sure you want to remove this book from the catalog?');">
64
- <button type="submit" class="p-3 text-rose-600 hover:text-rose-700 hover:bg-rose-50 rounded-xl transition-colors">
65
- <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
66
- </button>
67
- </form>
68
  </div>
69
  </div>
70
  </div>
 
46
  </div>
47
 
48
  <div class="flex items-center gap-4 pt-4">
49
+ @if (User.IsInRole("Member") || !User.Identity.IsAuthenticated)
50
  {
51
+ @if (Model.AvailableCopies > 0)
52
+ {
53
+ <form asp-controller="Borrowings" asp-action="Borrow" method="POST">
54
+ <input type="hidden" name="BookId" value="@Model.Id" />
55
+ <button type="submit" class="btn-primary py-3 px-8 text-base shadow-lg shadow-slate-200">Borrow This Book</button>
56
+ </form>
57
+ }
58
+ else
59
+ {
60
+ <button disabled class="btn-primary py-3 px-8 text-base opacity-50 cursor-not-allowed">Out of Stock</button>
61
+ }
62
  }
63
+
64
+ @if (User.IsInRole("Librarian"))
65
  {
66
+ <a asp-action="Edit" asp-route-id="@Model.Id" class="btn-secondary py-3 px-8 text-base">Edit Details</a>
67
+
68
+ <form asp-action="Delete" asp-route-id="@Model.Id" method="POST" onsubmit="return confirm('Are you sure you want to remove this book from the catalog?');">
69
+ <button type="submit" class="p-3 text-rose-600 hover:text-rose-700 hover:bg-rose-50 rounded-xl transition-colors">
70
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
71
+ </button>
72
+ </form>
73
  }
 
 
 
 
 
 
 
 
74
  </div>
75
  </div>
76
  </div>
Frontend/Views/Books/Index.cshtml CHANGED
@@ -8,12 +8,15 @@
8
  <h1 class="text-3xl font-bold tracking-tight text-slate-900">Books Catalog</h1>
9
  <p class="mt-2 text-slate-500">Explore and discover your next great read.</p>
10
  </div>
11
- <div class="flex items-center gap-3">
12
- <a asp-action="Create" class="btn-primary">
13
- <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
14
- Add New Book
15
- </a>
16
- </div>
 
 
 
17
  </div>
18
 
19
  <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-10">
@@ -64,9 +67,12 @@
64
  </div>
65
  <div class="mt-4 pt-4 border-t border-slate-50 flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
66
  <a asp-action="Details" asp-route-id="@book.Id" class="flex-1 btn-secondary py-1 text-[11px]">Details</a>
67
- <a asp-action="Edit" asp-route-id="@book.Id" class="p-1.5 rounded-md border border-slate-200 hover:bg-slate-50 text-slate-600">
68
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
69
- </a>
 
 
 
70
  </div>
71
  </div>
72
  </div>
 
8
  <h1 class="text-3xl font-bold tracking-tight text-slate-900">Books Catalog</h1>
9
  <p class="mt-2 text-slate-500">Explore and discover your next great read.</p>
10
  </div>
11
+ @if (User.IsInRole("Librarian"))
12
+ {
13
+ <div class="flex items-center gap-3">
14
+ <a asp-action="Create" class="btn-primary">
15
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
16
+ Add New Book
17
+ </a>
18
+ </div>
19
+ }
20
  </div>
21
 
22
  <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-10">
 
67
  </div>
68
  <div class="mt-4 pt-4 border-t border-slate-50 flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
69
  <a asp-action="Details" asp-route-id="@book.Id" class="flex-1 btn-secondary py-1 text-[11px]">Details</a>
70
+ @if (User.IsInRole("Librarian"))
71
+ {
72
+ <a asp-action="Edit" asp-route-id="@book.Id" class="p-1.5 rounded-md border border-slate-200 hover:bg-slate-50 text-slate-600">
73
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
74
+ </a>
75
+ }
76
  </div>
77
  </div>
78
  </div>
Frontend/Views/Borrowings/Index.cshtml CHANGED
@@ -14,7 +14,7 @@
14
  <thead>
15
  <tr class="bg-slate-50/50 border-b border-slate-100">
16
  <th class="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-widest">Book Information</th>
17
- @if (User.IsInRole("Admin"))
18
  {
19
  <th class="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-widest">Member</th>
20
  }
@@ -28,7 +28,7 @@
28
  @if (!Model.Any())
29
  {
30
  <tr>
31
- <td colspan="@(User.IsInRole("Admin") ? 6 : 5)" class="px-6 py-12 text-center text-slate-400 italic">
32
  You don't have any borrowing history yet.
33
  </td>
34
  </tr>
@@ -47,7 +47,7 @@
47
  </div>
48
  </div>
49
  </td>
50
- @if (User.IsInRole("Admin"))
51
  {
52
  <td class="px-6 py-4">
53
  <div class="text-sm font-medium text-slate-900 truncate">@loan.UserEmail</div>
@@ -66,8 +66,10 @@
66
  </td>
67
  <td class="px-6 py-4">
68
  <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
69
- @(loan.Status == "Borrowed" ? "bg-amber-100 text-amber-700" : "bg-emerald-100 text-emerald-700")">
70
- @loan.Status
 
 
71
  </span>
72
  </td>
73
  <td class="px-6 py-4">
@@ -91,11 +93,15 @@
91
  <td class="px-6 py-4 text-right">
92
  @if (loan.Status == "Borrowed")
93
  {
94
- <form asp-action="Return" method="POST" onsubmit="return confirm('Ready to return this book?');">
95
  <input type="hidden" name="id" value="@loan.Id" />
96
- <button type="submit" class="btn-secondary py-1.5 px-4 text-xs font-bold">@(User.IsInRole("Admin") ? "Process Return" : "Return Book")</button>
97
  </form>
98
  }
 
 
 
 
99
  </td>
100
  </tr>
101
  }
 
14
  <thead>
15
  <tr class="bg-slate-50/50 border-b border-slate-100">
16
  <th class="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-widest">Book Information</th>
17
+ @if (User.IsInRole("Librarian"))
18
  {
19
  <th class="px-6 py-4 text-[10px] font-bold text-slate-400 uppercase tracking-widest">Member</th>
20
  }
 
28
  @if (!Model.Any())
29
  {
30
  <tr>
31
+ <td colspan="@(User.IsInRole("Librarian") ? 6 : 5)" class="px-6 py-12 text-center text-slate-400 italic">
32
  You don't have any borrowing history yet.
33
  </td>
34
  </tr>
 
47
  </div>
48
  </div>
49
  </td>
50
+ @if (User.IsInRole("Librarian"))
51
  {
52
  <td class="px-6 py-4">
53
  <div class="text-sm font-medium text-slate-900 truncate">@loan.UserEmail</div>
 
66
  </td>
67
  <td class="px-6 py-4">
68
  <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
69
+ @(loan.Status == "Borrowed" ? "bg-amber-100 text-amber-700" :
70
+ loan.Status == "PendingReturn" ? "bg-blue-100 text-blue-700" :
71
+ "bg-emerald-100 text-emerald-700")">
72
+ @(loan.Status == "PendingReturn" ? "Pending Approval" : loan.Status)
73
  </span>
74
  </td>
75
  <td class="px-6 py-4">
 
93
  <td class="px-6 py-4 text-right">
94
  @if (loan.Status == "Borrowed")
95
  {
96
+ <form asp-action="RequestReturn" method="POST" onsubmit="return confirm('Request return for this book? It will be marked as pending until approved.');">
97
  <input type="hidden" name="id" value="@loan.Id" />
98
+ <button type="submit" class="btn-secondary py-1.5 px-4 text-xs font-bold">Return Book</button>
99
  </form>
100
  }
101
+ else if (loan.Status == "PendingReturn")
102
+ {
103
+ <span class="text-xs font-medium text-slate-400">Awaiting Approval</span>
104
+ }
105
  </td>
106
  </tr>
107
  }
Frontend/Views/Borrowings/Manage.cshtml CHANGED
@@ -84,8 +84,10 @@
84
  </td>
85
  <td class="px-6 py-5 text-center">
86
  <span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider
87
- @(loan.Status == "Borrowed" ? "bg-blue-100 text-blue-700" : "bg-emerald-100 text-emerald-700")">
88
- @loan.Status
 
 
89
  </span>
90
  </td>
91
  <td class="px-6 py-5">
@@ -111,24 +113,31 @@
111
  }
112
  </td>
113
  <td class="px-6 py-5 text-right">
114
- @if (loan.Status == "Borrowed")
115
  {
116
- <form asp-action="Return" method="POST" onsubmit="return confirm('Process return for this book?');">
 
 
 
 
 
 
 
 
 
 
 
117
  <input type="hidden" name="id" value="@loan.Id" />
118
  <input type="hidden" name="fromManage" value="true" />
119
  <button type="submit" class="inline-flex items-center gap-2 bg-slate-900 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-slate-800 transition-all shadow-sm">
120
- <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M11 19l-7-7 7-7m8 14l-7-7 7-7"></path></svg>
121
  Process Return
122
  </button>
123
  </form>
124
  }
125
  else
126
  {
127
- <div class="group relative inline-block">
128
- <div class="p-2 bg-slate-100 rounded-full text-slate-400 cursor-help">
129
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
130
- </div>
131
- <span class="absolute hidden group-hover:block bg-slate-800 text-white text-[9px] px-2 py-1 rounded right-0 top-full mt-1 whitespace-nowrap z-10">Completed</span>
132
  </div>
133
  }
134
  </td>
 
84
  </td>
85
  <td class="px-6 py-5 text-center">
86
  <span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider
87
+ @(loan.Status == "Borrowed" ? "bg-amber-100 text-amber-700" :
88
+ loan.Status == "PendingReturn" ? "bg-blue-600 text-white shadow-sm" :
89
+ "bg-emerald-100 text-emerald-700")">
90
+ @(loan.Status == "PendingReturn" ? "Pending Approval" : loan.Status)
91
  </span>
92
  </td>
93
  <td class="px-6 py-5">
 
113
  }
114
  </td>
115
  <td class="px-6 py-5 text-right">
116
+ @if (loan.Status == "PendingReturn")
117
  {
118
+ <form asp-action="Return" method="POST" onsubmit="return confirm('Approve this return?');">
119
+ <input type="hidden" name="id" value="@loan.Id" />
120
+ <input type="hidden" name="fromManage" value="true" />
121
+ <button type="submit" class="inline-flex items-center gap-2 bg-emerald-600 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-emerald-700 transition-all shadow-md">
122
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg>
123
+ Approve Return
124
+ </button>
125
+ </form>
126
+ }
127
+ else if (loan.Status == "Borrowed")
128
+ {
129
+ <form asp-action="Return" method="POST" onsubmit="return confirm('Force process return for this book?');">
130
  <input type="hidden" name="id" value="@loan.Id" />
131
  <input type="hidden" name="fromManage" value="true" />
132
  <button type="submit" class="inline-flex items-center gap-2 bg-slate-900 text-white px-4 py-2 rounded-lg text-xs font-bold hover:bg-slate-800 transition-all shadow-sm">
 
133
  Process Return
134
  </button>
135
  </form>
136
  }
137
  else
138
  {
139
+ <div class="group relative inline-block text-emerald-600">
140
+ <svg class="w-6 h-6 ml-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
 
 
 
141
  </div>
142
  }
143
  </td>
Frontend/Views/Borrowings/Rewards.cshtml ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @model IEnumerable<Frontend.Models.Dtos.LoyaltyRewardDto>
2
+ @{
3
+ ViewData["Title"] = "Loyalty Rewards";
4
+ }
5
+
6
+ <div class="mb-10">
7
+ <div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
8
+ <div>
9
+ <h1 class="text-3xl font-extrabold tracking-tight text-slate-900 border-b border-slate-100 pb-4 mb-2">Loyalty Rewards</h1>
10
+ <p class="text-slate-500 font-medium">Redeem your hard-earned points for exclusive library benefits.</p>
11
+ </div>
12
+ <div class="flex items-center gap-4 bg-amber-50 border border-amber-100 px-6 py-4 rounded-2xl shadow-sm">
13
+ <div class="p-2 bg-amber-100 rounded-lg text-amber-600">
14
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"></path></svg>
15
+ </div>
16
+ <div>
17
+ <p class="text-[10px] font-bold text-amber-600 uppercase tracking-widest mb-0.5">Your Balance</p>
18
+ @* This would ideally be fetched from a global layout or specific viewbag *@
19
+ <p class="text-2xl font-black text-slate-900">Points</p>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
26
+ @if (!Model.Any())
27
+ {
28
+ <div class="col-span-full py-20 text-center bg-slate-50 rounded-3xl border-2 border-dashed border-slate-200">
29
+ <div class="text-slate-400 mb-4">
30
+ <svg class="w-16 h-16 mx-auto opacity-20" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg>
31
+ </div>
32
+ <p class="text-slate-500 font-medium italic">No active rewards available at the moment. Check back soon!</p>
33
+ </div>
34
+ }
35
+
36
+ @foreach (var reward in Model)
37
+ {
38
+ <div class="group relative card p-6 hover:shadow-2xl hover:-translate-y-1 transition-all duration-300 border-t-4 border-amber-400">
39
+ <div class="flex justify-between items-start mb-6">
40
+ <div class="p-3 bg-amber-50 rounded-xl text-amber-600 group-hover:bg-amber-400 group-hover:text-white transition-colors">
41
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path></svg>
42
+ </div>
43
+ <div class="text-right">
44
+ <span class="text-3xl font-black text-slate-900">@reward.PointCost</span>
45
+ <span class="text-[10px] block font-bold text-slate-400 uppercase tracking-tighter">Points Required</span>
46
+ </div>
47
+ </div>
48
+
49
+ <h3 class="text-lg font-bold text-slate-900 mb-2 truncate">@reward.Name</h3>
50
+ <p class="text-sm text-slate-500 leading-relaxed mb-6 h-12 overflow-hidden line-clamp-2">@reward.Description</p>
51
+
52
+ <div class="flex items-center justify-between pt-6 border-t border-slate-50">
53
+ <div class="flex flex-col">
54
+ <span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Stock</span>
55
+ <span class="text-sm font-bold text-slate-700">@reward.StockQuantity available</span>
56
+ </div>
57
+ <button class="bg-slate-900 text-white px-5 py-2.5 rounded-xl text-xs font-bold hover:bg-slate-800 hover:shadow-lg transition-all active:scale-95">
58
+ Redeem Reward
59
+ </button>
60
+ </div>
61
+
62
+ @if (reward.StockQuantity < 10)
63
+ {
64
+ <div class="absolute top-2 right-2 flex">
65
+ <span class="animate-pulse bg-rose-500 text-white text-[8px] font-black px-1.5 py-0.5 rounded uppercase">Limited</span>
66
+ </div>
67
+ }
68
+ </div>
69
+ }
70
+ </div>
71
+
72
+ <div class="mt-12 p-8 bg-slate-900 rounded-3xl text-white relative overflow-hidden">
73
+ <div class="relative z-10 flex flex-col md:flex-row items-center justify-between gap-8">
74
+ <div class="text-center md:text-left">
75
+ <h2 class="text-2xl font-bold mb-2">How to earn more points?</h2>
76
+ <p class="text-slate-400 max-w-md">Borrow books, subscribe to memberships, and return books on time to stack up your points and unlock even bigger rewards!</p>
77
+ </div>
78
+ <a asp-controller="Books" asp-action="Index" class="bg-amber-400 text-slate-900 px-8 py-4 rounded-2xl font-black hover:bg-amber-300 transition-all shadow-xl shadow-amber-400/20 whitespace-nowrap">
79
+ Browse Books Now
80
+ </a>
81
+ </div>
82
+ <!-- Decorative background element -->
83
+ <div class="absolute -right-10 -bottom-10 w-64 h-64 bg-amber-400/10 rounded-full blur-3xl"></div>
84
+ <div class="absolute -left-10 -top-10 w-48 h-48 bg-blue-400/5 rounded-full blur-3xl"></div>
85
+ </div>
Frontend/Views/Home/Index.cshtml CHANGED
@@ -10,26 +10,31 @@
10
  @if (User.Identity?.IsAuthenticated == true)
11
  {
12
  <!-- Loyalty Banner -->
13
- <div class="bg-gradient-to-r from-amber-500 to-amber-700 rounded-2xl p-6 mb-10 text-white shadow-lg relative overflow-hidden group">
14
- <div class="relative z-10 flex items-center justify-between">
15
- <div>
16
- <p class="text-amber-100 text-sm font-bold uppercase tracking-widest mb-1">Your Loyalty Status</p>
17
- <div class="flex items-baseline gap-3">
18
- <h2 class="text-4xl font-extrabold">@ViewBag.LoyaltyPoints</h2>
19
- <span class="text-amber-200 font-medium tracking-wide">Points</span>
 
 
 
20
  </div>
21
- <p class="text-[10px] text-amber-300/60 mt-1 uppercase tracking-tighter">Connection: @ViewBag.LoyaltyStatus</p>
22
- </div>
23
- <div class="text-right">
24
- <div class="inline-flex items-center gap-2 bg-black/20 backdrop-blur-sm px-4 py-2 rounded-full border border-white/10 shadow-inner">
25
- <svg class="w-5 h-5 text-amber-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path></svg>
26
- <span class="font-bold text-lg">@ViewBag.LoyaltyTier</span>
 
 
27
  </div>
28
  </div>
 
 
29
  </div>
30
- <!-- Decorative Background -->
31
- <div class="absolute -right-10 -top-10 w-48 h-48 bg-white/10 rounded-full blur-2xl transform group-hover:scale-110 transition-transform duration-700"></div>
32
- </div>
33
  }
34
 
35
  <!-- Stats Grid -->
 
10
  @if (User.Identity?.IsAuthenticated == true)
11
  {
12
  <!-- Loyalty Banner -->
13
+ <a asp-controller="Borrowings" asp-action="Rewards" class="block group">
14
+ <div class="bg-gradient-to-r from-amber-500 to-amber-700 rounded-2xl p-6 mb-10 text-white shadow-lg relative overflow-hidden transition-all duration-300 hover:shadow-xl hover:translate-y-[-2px] border border-white/10">
15
+ <div class="relative z-10 flex items-center justify-between">
16
+ <div>
17
+ <p class="text-amber-100 text-sm font-bold uppercase tracking-widest mb-1">Your Loyalty Status</p>
18
+ <div class="flex items-baseline gap-3">
19
+ <h2 class="text-4xl font-extrabold">@ViewBag.LoyaltyPoints</h2>
20
+ <span class="text-amber-200 font-medium tracking-wide">Points</span>
21
+ </div>
22
+ <p class="text-[10px] text-amber-300/60 mt-1 uppercase tracking-tighter">Connection: @ViewBag.LoyaltyStatus</p>
23
  </div>
24
+ <div class="text-right flex items-center gap-4">
25
+ <div class="inline-flex items-center gap-2 bg-black/20 backdrop-blur-sm px-4 py-2 rounded-full border border-white/10 shadow-inner">
26
+ <svg class="w-5 h-5 text-amber-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path></svg>
27
+ <span class="font-bold text-lg">@ViewBag.LoyaltyTier</span>
28
+ </div>
29
+ <div class="p-2 bg-white/20 rounded-full group-hover:bg-white/30 transition-colors">
30
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
31
+ </div>
32
  </div>
33
  </div>
34
+ <!-- Decorative Background -->
35
+ <div class="absolute -right-10 -top-10 w-48 h-48 bg-white/10 rounded-full blur-2xl transform group-hover:scale-110 transition-transform duration-700"></div>
36
  </div>
37
+ </a>
 
 
38
  }
39
 
40
  <!-- Stats Grid -->