Skip to content

Commit

Permalink
Merge pull request #37 from mihaiborbea/20-add-images-upload-for-recipes
Browse files Browse the repository at this point in the history
#20 add images upload for recipes
  • Loading branch information
mihaiborbea authored Nov 12, 2023
2 parents 646f267 + 5544193 commit 7d7cfb7
Show file tree
Hide file tree
Showing 16 changed files with 1,952 additions and 1,343 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
with:
node-version: 18.18
- name: npm dependencies
run: npm install
run: npm install --force
- name: Build
run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
Expand Down
3,131 changes: 1,831 additions & 1,300 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@angular/common": "^16.2.7",
"@angular/compiler": "^16.2.7",
"@angular/core": "^16.2.7",
"@angular/fire": "^7.6.1",
"@angular/fire": "^16.0.0",
"@angular/forms": "^16.2.7",
"@angular/material": "^16.2.6",
"@angular/material-moment-adapter": "^16.2.6",
Expand All @@ -27,8 +27,9 @@
"@ngrx/effects": "^16.2.0",
"@ngrx/store": "^16.2.0",
"@ngrx/store-devtools": "^16.2.0",
"firebase": "10.4.0",
"rxfire": "6.0.5",
"firebase": "^10.6.0",
"ngx-material-file-input": "^4.0.1",
"rxfire": "^6.0.5",
"rxjs": "~7.8.1",
"tslib": "^2.6.2",
"zone.js": "~0.13.0"
Expand Down
1 change: 0 additions & 1 deletion src/app/auth/auth.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export class AuthComponent implements OnDestroy, OnInit {
this.store
.pipe(select(selectAuthError), takeUntil(this.destroy$))
.subscribe((err) => {
console.log('ERRROR HEEERREE', err);
if (err && err !== AuthErrorCodesMessages.NotLoggedIn) {
this.showErrorAlert(err);
}
Expand Down
1 change: 0 additions & 1 deletion src/app/auth/state/auth.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ export class AuthEffects {
}

private handleError(errorRes: any) {
console.log('HEREEEEEEEEE', errorRes);
let errorMsg = 'An unknown error occurred!';
if (!errorRes.code) {
return of(AuthActions.authenticateFail({ errorMessage: errorMsg }));
Expand Down
7 changes: 6 additions & 1 deletion src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { getApp, initializeApp, provideFirebaseApp } from '@angular/fire/app';
import {
browserLocalPersistence,
getAuth,
provideAuth,
} from '@angular/fire/auth';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';
import { getStorage, provideStorage } from '@angular/fire/storage';

import { metaReducers } from './state/meta.reducers';
import { CoreEffects } from './state/core.effects';
Expand All @@ -25,6 +26,10 @@ import { appReducer } from './state/app.store';
return auth;
}),
provideFirestore(() => getFirestore()),
provideStorage(() => {
const app = getApp();
return getStorage(app);
}),
StoreModule.forRoot(appReducer, {
metaReducers: metaReducers,
}),
Expand Down
33 changes: 33 additions & 0 deletions src/app/core/services/fileupload.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import {
Storage,
getDownloadURL,
ref,
uploadBytesResumable,
} from '@angular/fire/storage';

import { FileUpload } from '../../shared/domain/fileupload.model';

@Injectable({ providedIn: 'root' })
export class FileUploadService {
private basePath = '/uploads';

constructor(private storage: Storage) {}

async pushFileToStorage(
fileUpload: FileUpload,
collection?: string
): Promise<string> {
const filePath = `${collection ? collection : this.basePath}/${
'' + Date.now() + fileUpload.file.name
}`;
const storageRef = ref(this.storage, filePath);
const uploadTask = uploadBytesResumable(storageRef, fileUpload.file);

const uploadedFile = await uploadTask;

const downloadURL = await getDownloadURL(uploadedFile.ref);

return downloadURL;
}
}
9 changes: 6 additions & 3 deletions src/app/recipes/domain/recipe.model.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { FileUpload } from '../../shared/domain/fileupload.model';
import { Ingredient } from '../../shared/domain/ingredient.model';

export class Recipe {
constructor(
public id: string,
public name: string,
public description: string,
public imagePath: string,
public image: FileUpload,
public ingredients: Ingredient[],
public createdBy: string
) {}
}

export const recipeConverter = {
toFirestore: (recipe: Recipe) => {
const allowedImage = { ...recipe.image };
delete allowedImage.file;
return {
name: recipe.name,
description: recipe.description,
imagePath: recipe.imagePath,
image: allowedImage,
ingredients: recipe.ingredients.map((i) => ({ ...i })),
createdBy: recipe.createdBy,
};
Expand All @@ -27,7 +30,7 @@ export const recipeConverter = {
snapshot.id,
data.name,
data.description,
data.imagePath,
new FileUpload(data.image.name, data.image.url, null),
data.ingredients.map((i) => new Ingredient(i.name, i.amount)),
data.createdBy
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/recipes/recipe-detail/recipe-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h1>{{ recipe.name }}</h1>
</div>
<div>
<div class="mb-3">
<img [src]="recipe.imagePath" alt="" />
<img [src]="recipe?.image?.url" alt="" />
</div>
</div>
<div>
Expand Down
39 changes: 19 additions & 20 deletions src/app/recipes/recipe-edit/recipe-edit.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,37 @@
</button>
</div>
<mat-form-field class="flex flex-row w-full mt-2 mb-1">
<mat-label for="name">Name</mat-label>
<input matInput type="text" id="name" name="name" formControlName="name" />
<input matInput type="text" id="name" name="name" formControlName="name" placeholder="Recipe name" />
<mat-icon class="text-zinc-400" matSuffix>restaurant_menu</mat-icon>
</mat-form-field>
<mat-form-field class="flex flex-row w-full mt-1 mb-1">
<mat-label for="imagePath">Image URL</mat-label>
<input matInput type="text" id="imagePath" name="imagePath" formControlName="imagePath" #imagePath />
</mat-form-field>
<div>
<img [src]="imagePath.value" alt="" />
<mat-form-field class="flex flex-row w-full mt-1 mb-2">
<ngx-mat-file-input [valuePlaceholder]="editMode ? 'Change image' :'Select image'" formControlName="image"
[accept]="'image/*'" />
<mat-icon class="text-zinc-400" matSuffix>folder</mat-icon>
</mat-form-field>
<div *ngIf="recipe?.image">
<img [src]="recipe.image.url" alt="" />
</div>
<mat-form-field class="flex flex-row w-full mt-1 mb-2">
<mat-label for="description">Description</mat-label>
<textarea matInput type="text" id="description" rows="4" formControlName="description"></textarea>
<textarea placeholder="Description" matInput type="text" id="description" rows="4"
formControlName="description"></textarea>
<mat-icon class="text-zinc-400" matSuffix>receipt</mat-icon>
</mat-form-field>
<div class="flex flex-col h-full">
<div formArrayName="ingredients">
<div *ngFor="let ingredients of controls; let i = index" [formGroupName]="i" class="flex flex-row gap-2 w-full">
<mat-form-field class="w-[80%] mt-1 mb-1">
<div *ngFor="let ingredients of ingredientsControls; let i = index" [formGroupName]="i" class="flex flex-row w-full">
<mat-form-field appearance="fill" class="w-[80%] mt-1 mb-1">
<input matInput type="text" placeholder="Ingredient name" formControlName="name" />
</mat-form-field>
<mat-form-field class="w-[20%] mt-1 mb-1">
<input matInput type="text" placeholder="Ingredient amount" formControlName="amount" />
<mat-form-field appearance="fill" class="w-[20%] mt-1 mb-1">
<input matInput type="text" placeholder="Amount" formControlName="amount" />
</mat-form-field>
<button mat-icon-button color="warn" type="button" class="mt-3" (click)="onDeleteIngredient(i)">
<mat-icon>close</mat-icon>
<button mat-icon-button color="accent" type="button" class="mt-3" (click)="onDeleteIngredient(i)">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div>
<mat-divider class="flex grow mb-3"></mat-divider>
</div>
<div>
<div class="flex flex-row gap-2">
<button mat-raised-button color="accent" type="button" (click)="onAddIngredient()">
Add Ingredient
</button>
Expand Down
18 changes: 12 additions & 6 deletions src/app/recipes/recipe-edit/recipe-edit.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Recipe } from '../domain/recipe.model';
import { AppState } from 'src/app/core/state/app.store';
import { User } from 'src/app/auth/domain/user.model';
import { selectAuthUser } from 'src/app/auth/state/auth.selectors';
import { FileUpload } from 'src/app/shared/domain/fileupload.model';

@Component({
selector: 'app-recipe-edit',
Expand All @@ -30,11 +31,12 @@ export class RecipeEditComponent implements OnInit, OnDestroy {
private store: Store<AppState>
) {}

get controls() {
get ingredientsControls() {
return (<UntypedFormArray>this.recipeForm.get('ingredients')).controls;
}

ngOnInit(): void {
console.log('HERE');
this.route.params
.pipe(
takeUntil(this.destroy$),
Expand Down Expand Up @@ -69,12 +71,17 @@ export class RecipeEditComponent implements OnInit, OnDestroy {
}

onSubmit() {
const recipeImage = new FileUpload(
this.recipeForm.get('image').value.files[0].name,
'gs://TBD',
this.recipeForm.get('image').value.files[0]
);
if (this.editMode) {
const editedRecipe = new Recipe(
this.recipe.id,
this.recipeForm.get('name').value,
this.recipeForm.get('description').value,
this.recipeForm.get('imagePath').value,
recipeImage,
this.recipeForm.get('ingredients').value,
this.currentUser.id
);
Expand All @@ -86,7 +93,7 @@ export class RecipeEditComponent implements OnInit, OnDestroy {
null,
this.recipeForm.get('name').value,
this.recipeForm.get('description').value,
this.recipeForm.get('imagePath').value,
recipeImage,
this.recipeForm.get('ingredients').value,
this.currentUser.id
);
Expand All @@ -105,13 +112,12 @@ export class RecipeEditComponent implements OnInit, OnDestroy {

private initForm() {
let recipeName = '';
let recipeImagePath = '';
let recipeImage = null;
let recipeDescription = '';
let recipeIngredients = new UntypedFormArray([]);

if (this.editMode) {
recipeName = this.recipe.name;
recipeImagePath = this.recipe.imagePath;
recipeDescription = this.recipe.description;
if (this.recipe.ingredients) {
recipeIngredients = new UntypedFormArray(
Expand All @@ -131,7 +137,7 @@ export class RecipeEditComponent implements OnInit, OnDestroy {

this.recipeForm = new UntypedFormGroup({
name: new UntypedFormControl(recipeName, Validators.required),
imagePath: new UntypedFormControl(recipeImagePath, Validators.required),
image: new UntypedFormControl(recipeImage, Validators.required),
description: new UntypedFormControl(
recipeDescription,
Validators.required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<div class="w-[70%] h-32 overflow-hidden p-0">
<span>{{ recipe.description }}</span>
</div>
<img class="w-[30%] h-[90%]" mat-card-image [src]="recipe.imagePath" [alt]="recipe.name" />
<img class="w-[30%] h-[90%]" mat-card-image [src]="recipe?.image?.url" [alt]="recipe.name" />
</mat-card-content>
</mat-card>
</a>
5 changes: 3 additions & 2 deletions src/app/recipes/recipes.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
<div class="max-w-[640px] w-[100%] md:w-[37%] md:flex" [ngClass]="{'hidden': hideList}">
<app-recipe-list></app-recipe-list>
</div>
<div class="flex flex-row w-[6%] md:hidden content-center" [ngClass]="{'hidden': hideBackBtn}" routerLink="/recipes">
<div class="flex flex-row w-[6%] md:hidden content-center" [ngClass]="{'hidden': hideBackBtn || !hideList }"
routerLink="/recipes">
<button type="button" class="flex flex-row p-0" mat-icon-button color="accent">
<mat-icon>arrow_back</mat-icon>
</button>
<span>Back</span>
</div>
<div class="max-w-[960px] w-[94%] md:w-[57%]" [ngClass]="{'hidden': !hideList, 'md:hidden': hideBackBtn}">
<div class="max-w-[960px] w-[94%] md:w-[57%]" [ngClass]="{'hidden': !hideList }">
<router-outlet></router-outlet>
</div>
</div>
33 changes: 30 additions & 3 deletions src/app/recipes/state/recipes.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import * as RecipesActions from './recipes.actions';
import { selectAuthUser } from 'src/app/auth/state/auth.selectors';
import { RecipesService } from '../services/recipes.service';
import { AppState } from 'src/app/core/state/app.store';
import { FileUploadService } from 'src/app/core/services/fileupload.service';
import { FileUpload } from 'src/app/shared/domain/fileupload.model';
@Injectable()
export class RecipesEffects {
fetchRecipes$ = createEffect(() =>
this.actions$.pipe(
ofType(RecipesActions.fetchRecipes, RecipesActions.createRecipe),
ofType(
RecipesActions.fetchRecipes,
RecipesActions.createRecipe,
RecipesActions.updateRecipe
),
withLatestFrom(this.store.select(selectAuthUser)),
switchMap(([_, user]) => this.recipesService.getUserRecipes(user.id)),
map((recipes: Recipe[]) => RecipesActions.setRecipes({ recipes }))
Expand All @@ -23,7 +29,27 @@ export class RecipesEffects {
() =>
this.actions$.pipe(
ofType(RecipesActions.updateRecipe, RecipesActions.createRecipe),
map((action) => this.recipesService.addOrUpdateRecipe(action.recipe))
map(async (action) => {
const uploadedFileUrl =
await this.fileUploadService.pushFileToStorage(
action.recipe.image,
'recipes'
);
const uploadedImage = new FileUpload(
action.recipe.image.name,
uploadedFileUrl,
action.recipe.image.file
);
const recipeWithUploadedImage = new Recipe(
action.recipe.id,
action.recipe.name,
action.recipe.description,
uploadedImage,
action.recipe.ingredients,
action.recipe.createdBy
);
return this.recipesService.addOrUpdateRecipe(recipeWithUploadedImage);
})
),
{
dispatch: false,
Expand Down Expand Up @@ -59,6 +85,7 @@ export class RecipesEffects {
constructor(
private actions$: Actions,
private store: Store<AppState>,
private recipesService: RecipesService
private recipesService: RecipesService,
private fileUploadService: FileUploadService
) {}
}
3 changes: 3 additions & 0 deletions src/app/shared/domain/fileupload.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class FileUpload {
constructor(public name: string, public url: string, public file: File) {}
}
2 changes: 2 additions & 0 deletions src/app/shared/material.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MaterialFileInputModule } from 'ngx-material-file-input';

const materialComponents = [
MatToolbarModule,
Expand All @@ -30,6 +31,7 @@ const materialComponents = [
MatProgressBarModule,
MatMenuModule,
MatProgressSpinnerModule,
MaterialFileInputModule,
];

@NgModule({
Expand Down

0 comments on commit 7d7cfb7

Please sign in to comment.