Skip to content

Commit

Permalink
feat: implement recipe search
Browse files Browse the repository at this point in the history
- Add recipe-list and recipe-search components
- Replace home component with recipe-search
- Add searchRecipes method to recipe service
- Add SearchRecipes method to RecipeController

gh-16

Co-authored-by: Syed Zaidi <[email protected]>
Co-authored-by: Zubair Shibly <[email protected]>
  • Loading branch information
3 people committed Nov 20, 2023
1 parent e3b1110 commit f07814d
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 49 deletions.
14 changes: 8 additions & 6 deletions client/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import { RouterModule } from '@angular/router';
import { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';
import { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';
import { AppComponent } from './app.component';
import { FridgeComponent } from './fridge/fridge.component';
import { HomeComponent } from './home/home.component';
import { RecipeSearchComponent } from './recipe-search/recipe-search.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { RecipeComponent } from './recipe/recipe.component';
import { FridgeComponent } from './fridge/fridge.component';
import { RecipeListComponent } from './recipe-list/recipe-list.component';

@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
FridgeComponent,
RecipeListComponent,
RecipeSearchComponent,
RecipeComponent,
],
imports: [
Expand All @@ -30,10 +32,10 @@ import { RecipeComponent } from './recipe/recipe.component';
UpperCasePipe,
ApiAuthorizationModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: '', component: RecipeSearchComponent, pathMatch: 'full' },
{ path: 'fridge', component: FridgeComponent },
{ path: 'recipe', component: RecipeComponent },
]),
{ path: 'recipe', component: RecipeComponent},
])
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true },
Expand Down
14 changes: 0 additions & 14 deletions client/src/app/home/home.component.html

This file was deleted.

8 changes: 0 additions & 8 deletions client/src/app/home/home.component.ts

This file was deleted.

11 changes: 3 additions & 8 deletions client/src/app/nav-menu/nav-menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"
>
<div class="container">
<a class="navbar-brand" [routerLink]="['/']">server</a>
<a class="navbar-brand" [routerLink]="['/']" (click)="navigateAndReload()"
>PantryPal</a
>
<button
class="navbar-toggler"
type="button"
Expand All @@ -20,13 +22,6 @@
[ngClass]="{ show: isExpanded }"
>
<ul class="navbar-nav flex-grow">
<li
class="nav-item"
[routerLinkActive]="['link-active']"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link text-dark" [routerLink]="['/']">Home</a>
</li>
<li class="nav-item" [routerLinkActive]="['link-active']">
<a class="nav-link text-dark" [routerLink]="['/fridge']">Fridge</a>
</li>
Expand Down
12 changes: 10 additions & 2 deletions client/src/app/nav-menu/nav-menu.component.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { Component } from '@angular/core';

import { Router } from '@angular/router';
@Component({
selector: 'app-nav-menu',
templateUrl: './nav-menu.component.html',
styleUrls: ['./nav-menu.component.scss']
styleUrls: ['./nav-menu.component.scss'],
})
export class NavMenuComponent {
isExpanded = false;

constructor(private router: Router) {}

collapse() {
this.isExpanded = false;
}

toggle() {
this.isExpanded = !this.isExpanded;
}

navigateAndReload() {
this.router.navigate(['/']).then(() => {
window.location.reload();
});
}
}
19 changes: 19 additions & 0 deletions client/src/app/recipe-list/recipe-list.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<ng-container *ngIf="recipeList.length != 0">
<ng-container *ngIf="recipeList.length == 1; else moreThanOne">
<h3>There is {{ recipeList.length }} recipe in the list.</h3>
<div *ngFor="let recipe of recipeList">
<a [routerLink]="['/recipe', recipe.id]">{{ recipe.name }}</a>
</div>
</ng-container>
<ng-template #moreThanOne>
<h3>
There are {{ recipeList.length }} recipes in the list that contain the term
'{{ searchedQuery }}'
</h3>
<div *ngFor="let recipe of recipeList">
<a>{{ recipe.name }}</a>
</div>
</ng-template>
</ng-container>

<!-- TODO (NilinReza): handle template -->
23 changes: 23 additions & 0 deletions client/src/app/recipe-list/recipe-list.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.center-container {
display: flex;
align-items: center;
height: 10vh; /* This will take the full height of the viewport */
}

.form-control {
border-radius: 25px; /* Rounded edges */
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); /* Shadow effect */
padding-left: 35px; /* Make room for the search icon */
width: 50vw; /* Increase the width of the search bar to 70% of the viewport width */
}
.form-outline {
position: relative; /* Allow the search icon and filter button to be positioned relative to this element */
margin-top: 10px;
margin-bottom: 10px; /* Add space after the search bar */
}
.form-outline i {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%); /* Vertically center the search icon */
}
23 changes: 23 additions & 0 deletions client/src/app/recipe-list/recipe-list.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { RecipeListComponent } from './recipe-list.component';

describe('RecipeListComponent', () => {
let component: RecipeListComponent;
let fixture: ComponentFixture<RecipeListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RecipeListComponent]
})
.compileComponents();

fixture = TestBed.createComponent(RecipeListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
16 changes: 16 additions & 0 deletions client/src/app/recipe-list/recipe-list.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component, Input } from '@angular/core';
import { Recipe, RecipeService } from '@services';

@Component({
selector: 'app-recipe-list',
templateUrl: './recipe-list.component.html',
styleUrls: ['./recipe-list.component.scss'],
})
export class RecipeListComponent {
@Input() public searchedQuery: string = '';
@Input() public recipeList: Recipe[] = [];

constructor(private recipeService: RecipeService) {}

//TODO : filter and sort has to be implemented
}
21 changes: 21 additions & 0 deletions client/src/app/recipe-search/recipe-search.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<h1>PantryPal</h1>
<p>Use the search bar below to search for any recipe</p>

<form (ngSubmit)="onSearch(inputText)">
<div class="form-outline">
<i class="fas fa-search"></i>
<input
type="text"
class="form-control"
name="inputText"
[(ngModel)]="inputText"
placeholder="Search Recipe.."
/>
</div>
<button type="submit">Search</button>
</form>

<app-recipe-list
[recipeList]="recipeList"
[searchedQuery]="searchText"
></app-recipe-list>
23 changes: 23 additions & 0 deletions client/src/app/recipe-search/recipe-search.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.center-container {
display: flex;
align-items: center;
height: 10vh; /* This will take the full height of the viewport */
}

.form-control {
border-radius: 25px; /* Rounded edges */
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); /* Shadow effect */
padding-left: 35px; /* Make room for the search icon */
width: 50vw; /* Increase the width of the search bar to 70% of the viewport width */
}
.form-outline {
position: relative; /* Allow the search icon and filter button to be positioned relative to this element */
margin-top: 10px;
margin-bottom: 10px; /* Add space after the search bar */
}
.form-outline i {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%); /* Vertically center the search icon */
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import { RecipeSearchComponent } from './recipe-search.component';

describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
describe('RecipeSearchComponent', () => {
let component: RecipeSearchComponent;
let fixture: ComponentFixture<RecipeSearchComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [RecipeSearchComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture = TestBed.createComponent(RecipeSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down
22 changes: 22 additions & 0 deletions client/src/app/recipe-search/recipe-search.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { Recipe, RecipeService } from '@services';

@Component({
selector: 'app-search',
templateUrl: './recipe-search.component.html',
styleUrls: ['./recipe-search.component.scss'],
})
export class RecipeSearchComponent {
public searchText = '';
public inputText = '';
public recipeList: Recipe[] = [];

constructor(private recipeService: RecipeService) {}

public onSearch(searchQuery: string) {
this.searchText = searchQuery;
this.recipeService
.searchRecipes(searchQuery)
.subscribe((recipeList: Recipe[]) => (this.recipeList = recipeList));
}
}
2 changes: 1 addition & 1 deletion client/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>server</title>
<title>PantryPal</title>
<base href="/" />

<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
9 changes: 7 additions & 2 deletions client/src/services/recipe/recipe.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { HttpClientService } from './../http-client/http-client.service';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

import { Observable, tap, BehaviorSubject } from 'rxjs';
export interface Recipe {
id: number;
name: string;
Expand All @@ -28,4 +27,10 @@ export class RecipeService {
public getRecipe(id: number): Observable<Recipe> {
return this.httpClientService.get<Recipe>(`recipe/${id}`);
}

public searchRecipes(searchQuery: string): Observable<Recipe[]> {
return this.httpClientService.post<Recipe[]>('recipe/search', {
searchQuery,
});
}
}
18 changes: 18 additions & 0 deletions server/Controllers/RecipeController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using server.Data;
using server.Models;

namespace server.Controllers
{
public class SearchModel
{
public string SearchQuery { get; set; }

Check warning on line 10 in server/Controllers/RecipeController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'SearchQuery' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 10 in server/Controllers/RecipeController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'SearchQuery' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}


[ApiController]
[Route("api/[controller]")]
public class RecipeController : ControllerBase
Expand Down Expand Up @@ -31,5 +38,16 @@ public async Task<IActionResult> GetDetails(int id)
return NotFound();
}
}

[HttpPost]
[Route("search")]
public async Task<IActionResult> SearchRecipes([FromBody] SearchModel searchModel)
{
var recipes = await _context.Recipe
.Where(r => r.Name.ToLower().Contains(searchModel.SearchQuery.ToLower()))
.ToListAsync();

return Ok(recipes);
}
}
}

0 comments on commit f07814d

Please sign in to comment.