diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy-prod.yml similarity index 89% rename from .github/workflows/deploy.yml rename to .github/workflows/deploy-prod.yml index 21df3e3..603f7a9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy-prod.yml @@ -1,8 +1,8 @@ -name: Build and Deploy DevEats +name: Build and Deploy DevEats in Prod on: push: - branches: [ "main", "issues/9" ] + branches: [ "main"] # pull_request: # branches: [ "issues/*" ] @@ -39,7 +39,6 @@ jobs: deploy: runs-on: ubuntu-latest needs: build-and-test - # if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Download artifact from build job @@ -58,16 +57,17 @@ jobs: --name deveats-wpc2025-test \ --resource-group deveats-wpc2025-rg \ --settings \ - ConnectionStrings__DefaultConnection="${{ secrets.CONNECTION_STRING_TEST }}" \ + ConnectionStrings__DefaultConnection="${{ secrets.CONNECTION_STRING_PROD }}" \ ASPNETCORE_ENVIRONMENT="Production" - name: Deploy to Azure Web App uses: azure/webapps-deploy@v3 with: app-name: 'deveats-wpc2025-test' - publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PROD }} package: . - name: Azure Logout run: az logout - if: always() \ No newline at end of file + if: always() + diff --git a/.github/workflows/deploy-test.yml b/.github/workflows/deploy-test.yml new file mode 100644 index 0000000..11a8f7b --- /dev/null +++ b/.github/workflows/deploy-test.yml @@ -0,0 +1,71 @@ +name: Build and Deploy DevEats in Test + +on: + pull_request: + + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Run Tests + run: dotnet test --no-build --verbosity normal --configuration Release + + - name: Publish + run: dotnet publish src/DevEats.Web/DevEats.Web.csproj -c Release -o ./publish + + - name: Upload artifact for deployment + uses: actions/upload-artifact@v4 + with: + name: deveats-app + path: ./publish + + deploy: + runs-on: ubuntu-latest + needs: build-and-test + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: deveats-app + + - name: Azure Login + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Set Azure Web App Settings + run: | + az webapp config appsettings set \ + --name deveats-wpc2025-test \ + --resource-group deveats-wpc2025-rg \ + --settings \ + ConnectionStrings__DefaultConnection="${{ secrets.CONNECTION_STRING_TEST }}" \ + ASPNETCORE_ENVIRONMENT="Production" + + - name: Deploy to Azure Web App + uses: azure/webapps-deploy@v3 + with: + app-name: 'deveats-wpc2025-test' + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_TEST }} + package: . + + - name: Azure Logout + run: az logout + if: always() + diff --git a/note.md b/note.md new file mode 100644 index 0000000..7537288 --- /dev/null +++ b/note.md @@ -0,0 +1,2 @@ +az ad sp create-for-rbac --name "deveats-wpc2025" --sdk-auth --role contributor \ + --scopes /subscriptions/403624a6-e899-4d2b-95df-ec6353be64a8/resourceGroups/deveats-wpc2025-rg \ No newline at end of file diff --git a/src/DevEats.Core/Interfaces/IRestaurantService.cs b/src/DevEats.Core/Interfaces/IRestaurantService.cs index 7a63c1e..62a1534 100644 --- a/src/DevEats.Core/Interfaces/IRestaurantService.cs +++ b/src/DevEats.Core/Interfaces/IRestaurantService.cs @@ -6,6 +6,7 @@ public interface IRestaurantService { Task GetByIdAsync(int id); Task> GetAllAsync(); + Task> GetRestaurantsPagedAsync(int pageNumber, int pageSize); Task AddAsync(Restaurant restaurant); Task UpdateAsync(Restaurant restaurant); Task DeleteAsync(int id); diff --git a/src/DevEats.Infrastructure/Services/RestaurantService.cs b/src/DevEats.Infrastructure/Services/RestaurantService.cs index bba2d63..4bfa619 100644 --- a/src/DevEats.Infrastructure/Services/RestaurantService.cs +++ b/src/DevEats.Infrastructure/Services/RestaurantService.cs @@ -29,6 +29,31 @@ public async Task> GetAllAsync() .ToListAsync(); } + public async Task> GetRestaurantsPagedAsync(int pageNumber, int pageSize) + { + if (pageNumber < 1) pageNumber = 1; + if (pageSize < 1) pageSize = 1; + + var query = _context.Restaurants + .Include(r => r.Reviews) + .OrderByDescending(r => r.AverageRating); + + var totalCount = await query.CountAsync(); + + var items = await query + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + return new PagedResult + { + Items = items, + TotalCount = totalCount, + PageNumber = pageNumber, + PageSize = pageSize + }; + } + public async Task AddAsync(Restaurant restaurant) { _context.Restaurants.Add(restaurant); diff --git a/src/DevEats.Web/Pages/Index.razor b/src/DevEats.Web/Pages/Index.razor index 77cf98e..624c38f 100644 --- a/src/DevEats.Web/Pages/Index.razor +++ b/src/DevEats.Web/Pages/Index.razor @@ -1,8 +1,7 @@ @page "/" @using DevEats.Core.Models -@using DevEats.Infrastructure.Data -@using Microsoft.EntityFrameworkCore -@inject DevEatsDbContext DbContext +@using DevEats.Core.Interfaces +@inject IRestaurantService RestaurantService @inject NavigationManager Navigation DevEats - Perché il refactoring a stomaco vuoto è pericoloso @@ -108,6 +107,37 @@ } + + @if (totalPages > 1) + { + + } } @@ -434,6 +464,102 @@ } } + /* Pagination Controls */ + .pagination-controls { + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + margin-top: 4rem; + padding-top: 2rem; + } + + .pagination-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, #ff6b6b, #ff8e53); + color: white; + border: none; + border-radius: 12px; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3); + position: relative; + } + + .pagination-btn:hover:not([disabled]) { + background: linear-gradient(135deg, #ff8e53, #feca57); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4); + } + + /* Enhanced focus indicator for keyboard navigation - WCAG 2.1 AA compliant */ + .pagination-btn:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.4), 0 0 0 5px rgba(255, 255, 255, 1); + } + + .pagination-btn:focus-visible { + outline: 3px solid #ff6b6b; + outline-offset: 2px; + box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.4); + } + + .pagination-btn[disabled] { + background: #cbd5e0; + color: #a0aec0; + cursor: not-allowed; + box-shadow: none; + opacity: 0.6; + } + + /* Improved disabled state contrast - WCAG 2.1 AA minimum 3:1 */ + .pagination-btn[disabled]:focus { + outline: 2px solid #718096; + outline-offset: 2px; + box-shadow: 0 0 0 3px rgba(113, 128, 150, 0.3); + } + + .pagination-btn i { + font-size: 1rem; + } + + .pagination-info { + color: #4a5568; + font-weight: 600; + font-size: 1rem; + min-width: 140px; + text-align: center; + padding: 0.5rem; + } + + /* High contrast mode support */ + @@media (prefers-contrast: high) { + .pagination-btn { + border: 2px solid currentColor; + } + + .pagination-btn:focus { + outline: 4px solid currentColor; + outline-offset: 3px; + } + } + + /* Reduced motion support */ + @@media (prefers-reduced-motion: reduce) { + .pagination-btn { + transition: none; + } + + .pagination-btn:hover:not([disabled]) { + transform: none; + } + } + /* Responsive */ @@media (max-width: 768px) { .hero-title { @@ -451,17 +577,68 @@ .section-title { font-size: 2rem; } + + /* Mobile pagination adjustments */ + .pagination-controls { + gap: 1rem; + flex-wrap: wrap; + } + + .pagination-btn { + padding: 0.625rem 1rem; + font-size: 0.9rem; + min-width: 44px; /* WCAG 2.1 AA minimum touch target size */ + min-height: 44px; + } + + .pagination-info { + min-width: 100px; + font-size: 0.9rem; + } } @code { + [SupplyParameterFromQuery(Name = "page")] + public int PageQuery { get; set; } = 1; + private List? restaurants; + private PagedResult pagedRestaurants = new(); + + private int currentPage = 1; + private const int pageSize = 3; + private int totalPages => pagedRestaurants.TotalPages; protected override async Task OnInitializedAsync() { - restaurants = await DbContext.Restaurants - .Include(r => r.Reviews) - .OrderByDescending(r => r.AverageRating) - .ToListAsync(); + currentPage = PageQuery > 0 ? PageQuery : 1; + await LoadRestaurants(); + } + + protected override async Task OnParametersSetAsync() + { + if (PageQuery != currentPage && PageQuery > 0) + { + currentPage = PageQuery; + await LoadRestaurants(); + } + } + + private async Task LoadRestaurants() + { + pagedRestaurants = await RestaurantService.GetRestaurantsPagedAsync(currentPage, pageSize); + restaurants = pagedRestaurants.Items.ToList(); + } + + private async Task ChangePage(int newPage) + { + if (newPage < 1 || newPage > totalPages || newPage == currentPage) + { + return; + } + + currentPage = newPage; + Navigation.NavigateTo($"/?page={currentPage}"); + await LoadRestaurants(); } } diff --git a/src/DevEats.Web/Program.cs b/src/DevEats.Web/Program.cs index da74ec0..f83e3cd 100644 --- a/src/DevEats.Web/Program.cs +++ b/src/DevEats.Web/Program.cs @@ -7,6 +7,7 @@ var builder = WebApplication.CreateBuilder(args); +// Explicitly configure appsettings.json files builder.Configuration .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)