Skip to content

Commit

Permalink
Implement adding, editing and displaying plants (#6)
Browse files Browse the repository at this point in the history
* Displaying basic plant info

* Add plants service and plant delete button

* Add plants list element

* Change plants service directory name

* Implement adding new plants

* Add displaying image in PlantsListElement and deleting images

* Implement editing plants

* Change the way images are handled in db

* Fix adding image when adding plant
  • Loading branch information
Gojodzojo authored Jun 3, 2022
1 parent 80fda83 commit fc191f5
Show file tree
Hide file tree
Showing 39 changed files with 1,025 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .firebaserc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"projects": {
"default": "watering-reminder"
}
}
}
5 changes: 4 additions & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@
"ui": {
"enabled": true
}
},
"storage": {
"rules": "storage.rules"
}
}
}
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"@angular/service-worker": "~13.3.0",
"ngx-image-cropper": "^6.1.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
Expand All @@ -38,4 +39,4 @@
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.6.2"
}
}
}
4 changes: 4 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { map } from 'rxjs'
import { UnverifiedComponent } from './pages/unverified/unverified.component'
import { ForgotPasswordComponent } from './pages/forgot-password/forgot-password.component'
import { ActionComponent } from './pages/action/action.component'
import { AddPlantComponent } from './pages/add-plant/add-plant.component'
import { EditPlantComponent } from './pages/edit-plant/edit-plant.component'

const loggedInVerifiedUser: AuthPipeGenerator = () => (
map(user => {
Expand Down Expand Up @@ -36,6 +38,8 @@ const loggedOutUser: AuthPipeGenerator = () => (

const routes: Routes = [
{ path: 'action', component: ActionComponent },
{ path: 'add-plant', component: AddPlantComponent, ...canActivate(loggedInVerifiedUser) },
{ path: 'edit-plant/:id', component: EditPlantComponent, ...canActivate(loggedInVerifiedUser) },
{ path: 'dashboard', component: DashboardComponent, ...canActivate(loggedInVerifiedUser) },
{ path: 'login', component: LoginComponent, ...canActivate(loggedOutUser) },
{ path: 'register', component: RegisterComponent, ...canActivate(loggedOutUser) },
Expand Down
16 changes: 13 additions & 3 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { initializeApp, provideFirebaseApp } from '@angular/fire/app'
import { provideAuth, getAuth } from '@angular/fire/auth'
import { provideFirestore, getFirestore } from '@angular/fire/firestore'
import { provideMessaging, getMessaging } from '@angular/fire/messaging'
import { USE_EMULATOR } from '@angular/fire/compat/auth'
import { SETTINGS } from '@angular/fire/compat/firestore'
import { LoginComponent } from './pages/login/login.component'
import { RegisterComponent } from './pages/register/register.component'
import { IndexComponent } from './pages/index/index.component'
Expand All @@ -21,6 +19,12 @@ import { ForgotPasswordComponent } from './pages/forgot-password/forgot-password
import { ActionComponent } from './pages/action/action.component'
import { PasswordResetComponent } from './pages/action/modes/password-reset/password-reset.component'
import { EmailVerificationComponent } from './pages/action/modes/email-verification/email-verification.component'
import { PlantsListElementComponent } from './pages/dashboard/plants-list-element/plants-list-element.component'
import { AddPlantComponent } from './pages/add-plant/add-plant.component'
import { PlantFormComponent } from './plant-form/plant-form.component'
import { ImageCropperModule } from 'ngx-image-cropper'
import { getStorage, provideStorage } from '@angular/fire/storage';
import { EditPlantComponent } from './pages/edit-plant/edit-plant.component'

@NgModule({
declarations: [
Expand All @@ -34,12 +38,17 @@ import { EmailVerificationComponent } from './pages/action/modes/email-verificat
ActionComponent,
PasswordResetComponent,
EmailVerificationComponent,
PlantsListElementComponent,
AddPlantComponent,
PlantFormComponent,
EditPlantComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
ImageCropperModule,
ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production,
// Register the ServiceWorker as soon as the application is stable
Expand All @@ -49,7 +58,8 @@ import { EmailVerificationComponent } from './pages/action/modes/email-verificat
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideAuth(() => getAuth()),
provideFirestore(() => getFirestore()),
provideMessaging(() => getMessaging())
provideMessaging(() => getMessaging()),
provideStorage(() => getStorage()),
],
bootstrap: [AppComponent]
})
Expand Down
10 changes: 10 additions & 0 deletions src/app/models/plant.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface Plant {
id: string
name: string
description: string
timezone: string
waterTime: string
imageUrl: string
}

export type PlantWithoutImage = Omit<Plant, 'imageUrl'>
2 changes: 1 addition & 1 deletion src/app/pages/action/action.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { AuthService } from 'src/app/services/auth.service'
import { AuthService } from 'src/app/services/auth/auth.service'

enum Mode {
Loading,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { AuthService } from 'src/app/services/auth.service'
import { AuthService } from 'src/app/services/auth/auth.service'

enum Status {
Loading,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component } from '@angular/core'
import { FormControl } from '@angular/forms'
import { ActivatedRoute } from '@angular/router'
import { AuthService } from 'src/app/services/auth.service'
import { AuthService } from 'src/app/services/auth/auth.service'

enum Status {
WaitingForInput,
Expand Down
14 changes: 14 additions & 0 deletions src/app/pages/add-plant/add-plant.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<app-plant-form [formGroup]="form" [(imageUrl)]="imageUrl" [(isCropping)]="isCropping"></app-plant-form>
<br>
<button [disabled]="!canAddPlant" (click)="addPlant()">Add plant</button>
<button routerLink="/dashboard">Cancel</button>
<br>
<div [ngSwitch]="addPlantState">
<div *ngSwitchCase="AddPlantState.Loading">
Loading
</div>

<div *ngSwitchCase="AddPlantState.Error">
<p>Error adding plant</p>
</div>
</div>
Empty file.
25 changes: 25 additions & 0 deletions src/app/pages/add-plant/add-plant.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AddPlantComponent } from './add-plant.component';

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

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

beforeEach(() => {
fixture = TestBed.createComponent(AddPlantComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
48 changes: 48 additions & 0 deletions src/app/pages/add-plant/add-plant.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component } from '@angular/core'
import { FormControl, FormGroup, Validators } from '@angular/forms'
import { Router } from '@angular/router'
import { PlantsService } from 'src/app/services/plants/plants.service'

enum AddPlantState {
WaitingForInput,
Loading,
Error,
}

@Component({
selector: 'app-add-plant',
templateUrl: './add-plant.component.html',
styleUrls: ['./add-plant.component.scss']
})
export class AddPlantComponent {
AddPlantState = AddPlantState

form = new FormGroup({
name: new FormControl('', Validators.required),
description: new FormControl(''),
timezone: new FormControl(Intl.DateTimeFormat().resolvedOptions().timeZone, Validators.required),
waterTime: new FormControl('', Validators.required),
})
imageUrl = ''
isCropping = false
addPlantState = AddPlantState.WaitingForInput

constructor(public plantsService: PlantsService, public router: Router) { }

async addPlant() {
if (this.canAddPlant) {
try {
this.addPlantState = AddPlantState.Loading
await this.plantsService.addPlant(this.form.value, this.imageUrl)
await this.router.navigate(['/dashboard'])
} catch (error) {
console.error(error)
this.addPlantState = AddPlantState.Error
}
}
}

get canAddPlant() {
return this.form.valid && !this.isCropping
}
}
22 changes: 21 additions & 1 deletion src/app/pages/dashboard/dashboard.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
<p>dashboard works!</p>
<button (click)="logout()">Logout</button>
<button (click)="auth.logUser()">Log user</button>

<div *ngIf="isLoading; then loadingBlock else loadedBlock"></div>

<ng-template #loadingBlock>
<p>Loading</p>
</ng-template>

<ng-template #loadedBlock>
<div *ngIf="plantsService.plantsAreEmpty$ | async; then plantsEmptyBlock else plantsNotEmptyBlock"></div>

<ng-template #plantsEmptyBlock>
<p>You have no plants.</p>
<button routerLink="/add-plant">Add your first plant</button>
</ng-template>

<ng-template #plantsNotEmptyBlock>
<button routerLink="/add-plant">Add a plant</button>
<app-plants-list-element *ngFor="let plant of plantsService.plants$ | async" [plant]="plant">
</app-plants-list-element>
</ng-template>
</ng-template>
9 changes: 6 additions & 3 deletions src/app/pages/dashboard/dashboard.component.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { Component } from '@angular/core'
import { Router } from '@angular/router'
import { AuthService } from 'src/app/services/auth.service'
import { AuthService } from 'src/app/services/auth/auth.service'
import { PlantsService } from 'src/app/services/plants/plants.service'

@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent {
isLoading = true

constructor(public auth: AuthService, public router: Router) { }
constructor(public auth: AuthService, public router: Router, public plantsService: PlantsService) {
this.plantsService.plants$.subscribe(() => this.isLoading = false)
}

async logout() {
await this.auth.logout()
await this.router.navigate(['/login'])
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<h3>{{ plant.name }}</h3>
<p>{{ plant.description }} {{ plant.waterTime }}</p>
<button (click)="deletePlant()">Delete</button>
<p *ngIf="deleteState === DeleteState.Loading">Loading</p>
<p *ngIf="deleteState === DeleteState.Error">Could not delete plant</p>
<button [routerLink]="'/edit-plant/' + plant.id">Edit</button>
<img [src]="plant.imageUrl ? plant.imageUrl : 'assets/images/default_plant_image.png'" alt="plant image">
<hr>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PlantsListElementComponent } from './plants-list-element.component';

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

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

beforeEach(() => {
fixture = TestBed.createComponent(PlantsListElementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component, Input } from '@angular/core'
import { Plant } from 'src/app/models/plant.model'
import { PlantsService } from 'src/app/services/plants/plants.service'

enum DeleteState {
WaitingForInput,
Loading,
Error,
}

@Component({
selector: 'app-plants-list-element',
templateUrl: './plants-list-element.component.html',
styleUrls: ['./plants-list-element.component.scss']
})
export class PlantsListElementComponent {
@Input() plant!: Plant

DeleteState = DeleteState
deleteState = DeleteState.WaitingForInput

constructor(public plantsService: PlantsService) { }

async deletePlant() {
try {
this.deleteState = DeleteState.Loading
await this.plantsService.deletePlant(this.plant.id, !!this.plant.imageUrl)
}
catch (e) {
this.deleteState = DeleteState.Error
console.error(e)
}
}

}
Loading

0 comments on commit fc191f5

Please sign in to comment.