Repository: kritikos-io/Training.JobVacancy.Frontend
Pull request #22: Add new company form
Author: login: Costaknc
Created: 2025-03-04T15:09:24Z | Merged: 2025-03-05T20:44:07Z

Created a new page under the route "company/create", added some components in the navbar to be able to navigate correctly to that page, implemented a service and model to execute the API call to the server and made a simple form to create a new company using that API.


Diff:
diff --git a/proxy.conf.json b/proxy.conf.json
index 99d9b5b..0c40af3 100644
--- a/proxy.conf.json
+++ b/proxy.conf.json
@@ -1,5 +1,5 @@
 {
-  "/api": {
+  "/api/*": {
     "target": "https://wapp-training-prod-northeu-001.azurewebsites.net",
     "secure": true,
     "changeOrigin": true,
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index c97cac8..c47d2e6 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -17,6 +17,7 @@
       padding-bottom: 4rem;
       padding-left: 1.5rem !important;
       padding-right: 1.5rem !important;
+      align-items: center;
     }
   }
 }
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index c7b740e..121ac32 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -20,13 +20,15 @@ export const routes: Routes = [
     loadComponent: () => import('./features/playground').then(c => c.PlaygroundComponent),
   },
   {
-    path: 'candidates',
+    path: 'company/create',
     canActivate: [autoLoginPartialRoutesGuard, AuthGuard],
     data: {
-      role: [UserRole.VIEW_PROFILE, UserRole.ADMIN],
+      role: [UserRole.VIEW_PROFILE],
     },
-    loadComponent: () => import('./features/candidates').then(c => c.CandidatesComponent),
+    loadComponent: () =>
+      import('./features/company-create').then(c => c.CompanyCreateComponent),
   },
+
   {
     path: 'unauthorized',
     canActivate: [],
diff --git a/src/app/core/config/company-api.service.ts b/src/app/core/config/company-api.service.ts
new file mode 100644
index 0000000..983cf36
--- /dev/null
+++ b/src/app/core/config/company-api.service.ts
@@ -0,0 +1,30 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { CompanyModel } from '../models/company.model';
+
+@Injectable({
+  providedIn: 'root',
+})
+export class CompanyApiService {
+  constructor(private http: HttpClient) {}
+
+  createCompany(company: CompanyModel): Observable<CompanyModel> {
+    const newCompany = {
+      name: company.name,
+      website: company.website,
+      vat: company.vat,
+      logoUrl: company.logoUrl,
+      address: company.address,
+      isSponsored: company.isSponsored,
+      phoneNumber: company.phoneNumber,
+    };
+
+    console.log('Adding new company:', JSON.stringify(newCompany, null, 2));
+
+    return this.http.post<CompanyModel>('/api/company', newCompany, {
+      headers: { 'Content-Type': 'application/json' },
+    });
+  }
+}
diff --git a/src/app/core/models/company.model.ts b/src/app/core/models/company.model.ts
index c2efd74..18adaef 100644
--- a/src/app/core/models/company.model.ts
+++ b/src/app/core/models/company.model.ts
@@ -1,9 +1,17 @@
 export interface CompanyModel {
   name: string;
+  website: string;
+  vat: string;
   logoUrl: string;
-  location: string;
-  country: string;
-  isFeatured: boolean;
+  address: {
+    country: string;
+    city: string;
+    street: string;
+    streetNumber: string;
+    postalCode: string;
+  };
+  isSponsored: boolean;
+  phoneNumber: string;
   openPositions: number;
   openPositionsUrl: string;
 }
diff --git a/src/app/features/candidates/candidates.component.html b/src/app/features/candidates/candidates.component.html
deleted file mode 100644
index 614ae7c..0000000
--- a/src/app/features/candidates/candidates.component.html
+++ /dev/null
@@ -1 +0,0 @@
-<p>candidates works!</p>
diff --git a/src/app/features/candidates/candidates.component.ts b/src/app/features/candidates/candidates.component.ts
deleted file mode 100644
index 172a769..0000000
--- a/src/app/features/candidates/candidates.component.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'aa-candidates',
-  imports: [],
-  templateUrl: './candidates.component.html',
-})
-export class CandidatesComponent {}
diff --git a/src/app/features/candidates/index.ts b/src/app/features/candidates/index.ts
deleted file mode 100644
index 9ed6124..0000000
--- a/src/app/features/candidates/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './candidates.component';
diff --git a/src/app/features/company-create/company-create.component.html b/src/app/features/company-create/company-create.component.html
new file mode 100644
index 0000000..0dbeb47
--- /dev/null
+++ b/src/app/features/company-create/company-create.component.html
@@ -0,0 +1,201 @@
+<div
+  class="aa--rounded aa--text-start aa--px-8 aa--pt-6 aa--pb-8 aa--mb-4 aa--bg-gray-100 aa--w-full aa--max-w-2xl aa--text-center aa--border aa--rounded aa--border-gray-200 aa--flex-column"
+>
+  <div
+    class="aa--text-black aa--text-2xl aa--align-text-top aa--font-bold aa--origin-center"
+  >
+    Add a new company
+  </div>
+  <form [formGroup]="newCompany" (ngSubmit)="onSubmit()">
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="name"
+      >
+        Name
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="name"
+        formControlName="name"
+        type="text"
+        placeholder="Company's Name"
+        required
+        [class.aa--border-red-500]="isFieldInvalid('name')"
+      />
+      <p *ngIf="isFieldInvalid('name')" class="aa--text-red-500">Name is required.</p>
+    </div>
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="website"
+      >
+        Website
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="website"
+        type="text"
+        formControlName="website"
+        placeholder="Company's Website URL"
+        [class.aa--border-red-500]="isFieldInvalid('website')"
+      />
+      <p *ngIf="isFieldInvalid('website')" class="aa--text-red-500">Enter a valid URL.</p>
+    </div>
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="vat"
+      >
+        VAT
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="vat"
+        type="text"
+        formControlName="vat"
+        placeholder="Company's VAT Number"
+        required
+        [class.aa--border-red-500]="isFieldInvalid('vat')"
+      />
+      <p *ngIf="isFieldInvalid('vat')" class="aa--text-red-500">VAT is required.</p>
+    </div>
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="logoUrl"
+      >
+        Company Logo
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="logoUrl"
+        type="file"
+        formControlName="logoUrl"
+        accept="image/jpeg, image/png image/icon"
+      />
+    </div>
+    <div class="aa--mb-4" formGroupName="address">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="country"
+      >
+        Country
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="country"
+        type="text"
+        formControlName="country"
+        placeholder="Company's Country"
+      />
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="city"
+      >
+        City
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="city"
+        type="text"
+        formControlName="city"
+        placeholder="Company's City"
+      />
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="street"
+      >
+        Street Name
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="street"
+        type="text"
+        formControlName="street"
+        placeholder="Company's Street Name"
+      />
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="steetNumber"
+      >
+        Street Number
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="steetNumber"
+        type="text"
+        formControlName="streetNumber"
+        placeholder="Company's Street Number"
+      />
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="postalCode"
+      >
+        Postal Code
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="postalCode"
+        type="text"
+        formControlName="postalCode"
+        placeholder="Company's Postal Code"
+      />
+    </div>
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="isSponsored"
+      >
+        Is the job ad sponsored?
+      </label>
+      <div class="aa--flex aa--gap-4">
+        <label class="aa--inline-flex aa--items-center">
+          <input
+            type="radio"
+            formControlName="isSponsored"
+            required
+            value="true"
+            class="aa--mr-2"
+          />
+          Yes
+        </label>
+        <label class="aa--inline-flex aa--items-center">
+          <input
+            type="radio"
+            formControlName="isSponsored"
+            required
+            value="false"
+            class="aa--mr-2"
+          />
+          No
+        </label>
+        <p *ngIf="isFieldInvalid('isSponsored')" class="aa--text-red-500">
+          You must pick a choice.
+        </p>
+      </div>
+    </div>
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="phoneNumber"
+      >
+        Phone number
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="phoneNumber"
+        type="text"
+        formControlName="phoneNumber"
+        placeholder="Company's Phone Number"
+      />
+    </div>
+    <button
+      type="submit"
+      [disabled]="newCompany.invalid"
+      class="aa--justify-self-center hover:aa--bg-gray-200 aa--border aa--rounded aa--w-lg aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+    >
+      Submit
+    </button>
+  </form>
+</div>
diff --git a/src/app/features/company-create/company-create.component.ts b/src/app/features/company-create/company-create.component.ts
new file mode 100644
index 0000000..8131057
--- /dev/null
+++ b/src/app/features/company-create/company-create.component.ts
@@ -0,0 +1,68 @@
+import { CommonModule, NgIf } from '@angular/common';
+import { Component, inject, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { CompanyApiService } from '../../core/config/company-api.service';
+import { CompanyModel } from '../../core/models/company.model';
+
+@Component({
+  selector: 'aa-company-create',
+  imports: [ReactiveFormsModule, CommonModule, NgIf],
+  templateUrl: './company-create.component.html',
+})
+export class CompanyCreateComponent implements OnInit{
+  private fb = inject(FormBuilder);
+  private companyApi = inject(CompanyApiService);
+  
+  company!: CompanyModel;
+  newCompany!: FormGroup;
+
+  ngOnInit(): void {
+    this.initForm();
+  }
+
+  onSubmit() {
+    if (this.newCompany.invalid) {
+      alert('Some fields are not correct! Please check again.');
+      return;
+    }
+    this.company = this.newCompany.value as CompanyModel;
+    console.log('Form Submitted', this.company);
+    this.companyApi.createCompany(this.company).subscribe({
+      next: response => {
+        alert(`Company "${this.company.name}" created successfully!`);
+        console.log('Company created successfully:', response);
+      },
+      error: error => {
+        console.error('Error creating company:', error);
+        alert('Failed to create company. Please try again.');
+      },
+    });
+    this.newCompany.reset();
+  }
+
+  isFieldInvalid(field: string) {
+    const control = this.newCompany.get(field);
+    return !!(control && control.invalid && control.touched);
+  }
+
+  private initForm(): void {
+    this.newCompany = this.fb.group({
+      name: ['', Validators.required],
+      website: ['', [Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/)]],
+      vat: ['', Validators.required],
+      logoUrl: [''],
+      address: this.fb.group({
+        country: [''],
+        city: [''],
+        street: [''],
+        streetNumber: [''],
+        postalCode: [''],
+      }),
+      isSponsored: [false, Validators.required],
+      phoneNumber: ['', [Validators.pattern(/^\+?\d{10,15}$/)]],
+      openPositions: [0],
+      openPositionsUrl: [''],
+    });
+  }
+}
diff --git a/src/app/features/company-create/index.ts b/src/app/features/company-create/index.ts
new file mode 100644
index 0000000..45c6ec8
--- /dev/null
+++ b/src/app/features/company-create/index.ts
@@ -0,0 +1 @@
+export * from './company-create.component';
diff --git a/src/app/features/playground/playground.component.ts b/src/app/features/playground/playground.component.ts
index 460d2a5..1a74c37 100644
--- a/src/app/features/playground/playground.component.ts
+++ b/src/app/features/playground/playground.component.ts
@@ -20,9 +20,17 @@ export class PlaygroundComponent {
 
   company1: CompanyModel = {
     name: 'Acme Inc.',
-    location: 'San Francisco, CA',
-    country: 'U.S.A.',
-    isFeatured: true,
+    website: 'site.com',
+    vat: '1512512516',
+    address: {
+      country: 'U.S.A.',
+      city: 'San Francisco',
+      street: 'Street',
+      streetNumber: '69',
+      postalCode: '42069',
+    },
+    isSponsored: true,
+    phoneNumber: '1234567890',
     openPositions: 5,
     openPositionsUrl: '#',
     logoUrl: 'logo.jpg',
@@ -30,20 +38,36 @@ export class PlaygroundComponent {
 
   company2: CompanyModel = {
     name: 'Vasko Co',
-    location: 'Athens Argyroupoli',
-    country: 'Greece',
-    isFeatured: false,
-    openPositions: 2,
+    website: 'site.com',
+    vat: '1512512516',
+    address: {
+      country: 'Greece',
+      city: 'Argyroupoli',
+      street: 'Street',
+      streetNumber: '69',
+      postalCode: '42069',
+    },
+    isSponsored: true,
+    phoneNumber: '1234567890',
+    openPositions: 5,
     openPositionsUrl: '#',
     logoUrl: 'logo.jpg',
   };
 
   company3: CompanyModel = {
     name: 'Genera Co',
-    location: 'Athens',
-    country: 'Greece',
-    isFeatured: true,
-    openPositions: 3,
+    website: 'site.com',
+    vat: '1512512516',
+    address: {
+      country: 'Greece',
+      city: 'Athens',
+      street: 'Street',
+      streetNumber: '69',
+      postalCode: '42069',
+    },
+    isSponsored: false,
+    phoneNumber: '1234567890',
+    openPositions: 5,
     openPositionsUrl: '#',
     logoUrl: 'logo.jpg',
   };
diff --git a/src/app/shared/ui/cards/company-card/company-card.component.html b/src/app/shared/ui/cards/company-card/company-card.component.html
index 5776c4e..f5db630 100644
--- a/src/app/shared/ui/cards/company-card/company-card.component.html
+++ b/src/app/shared/ui/cards/company-card/company-card.component.html
@@ -14,7 +14,7 @@
       <div class="aa--inline-block aa--font-bold aa--me-2">
         {{ company.name }}
       </div>
-      @if (company.isFeatured) {
+      @if (company.isSponsored) {
         <div
           class="aa--inline-block aa--rounded-full aa--bg-pink-100 aa--px-3 aa--text-red-600"
         >
@@ -25,7 +25,9 @@
 
     <div class="aa--text-gray-400">
       <fa-icon [icon]="faLocationDot" class="aa--me-2" size="sm" />
-      {{ company.location }}, {{ company.country }}
+      {{ company.address.street }} {{ company.address.streetNumber }},
+      {{ company.address.postalCode }}, {{ company.address.city }},
+      {{ company.address.country }}
     </div>
   </div>
 
diff --git a/src/app/shared/ui/header/navbar/navbar.component.html b/src/app/shared/ui/header/navbar/navbar.component.html
index 61820b0..420f898 100644
--- a/src/app/shared/ui/header/navbar/navbar.component.html
+++ b/src/app/shared/ui/header/navbar/navbar.component.html
@@ -7,22 +7,53 @@
     <ng-container *ngFor="let item of MenuItems; let i = index">
       <div *ngIf="item.active; then content; else other"></div>
       <ng-template #content>
-        <li
-          class="active hover:aa--text-orange-500 [&.active]:aa--border-b-2 [&.active]:aa--border-blue-700 [&.active]:aa--font-semibold [&.active]:aa--text-blue-700"
-        >
-          <button class="aa--h-full aa--w-auto aa--px-2 aa--py-3">
+        <li (mouseenter)="toggleSubmenu(i)">
+          <button
+            class="active [&.active]:aa--border-b-2 [&.active]:aa--border-blue-700 [&.active]:aa--font-semibold [&.active]:aa--text-blue-700 aa--h-full hover:aa--text-orange-500 aa--w-auto aa--px-2 aa--py-3"
+            (click)="onClick(i)"
+          >
             {{ item.title }}
           </button>
+          <div (mouseleave)="closeSubmenu(i)">
+            <ul
+              *ngIf="item.submenu && item.showSubmenu"
+              class="aa--absolute aa--z-10 aa--mt-2 aa--w-48 aa--bg-white aa--border aa--rounded aa--shadow-md"
+            >
+              <li *ngFor="let subItem of item.submenu; let subIndex = index">
+                <button
+                  class="aa--block hover:aa--text-orange-500 aa--w-full aa--text-left aa--px-4 aa--py-2 hover:aa--bg-gray-200 [&.active]:aa--border-b-2 [&.active]:aa--border-blue-700 [&.active]:aa--font-semibold [&.active]:aa--text-blue-700"
+                  (click)="onClick(i, subIndex)"
+                >
+                  {{ subItem.title }}
+                </button>
+              </li>
+            </ul>
+          </div>
         </li>
       </ng-template>
       <ng-template #other>
-        <li class="hover:aa--text-orange-500">
+        <li (mouseenter)="toggleSubmenu(i)">
           <button
-            class="aa--h-full aa--w-auto aa--px-2 aa--py-3"
-            (click)="onClick(i); $event.preventDefault()"
+            class="aa--h-full hover:aa--text-orange-500 aa--w-auto aa--px-2 aa--py-3"
+            (click)="onClick(i)"
           >
             {{ item.title }}
           </button>
+          <div (mouseleave)="closeSubmenu(i)">
+            <ul
+              *ngIf="item.submenu && item.showSubmenu"
+              class="aa--absolute aa--z-10 aa--mt-2 aa--w-48 aa--bg-white aa--border aa--rounded aa--shadow-md"
+            >
+              <li *ngFor="let subItem of item.submenu; let subIndex = index">
+                <button
+                  class="aa--block hover:aa--text-orange-500 aa--w-full aa--text-left aa--px-4 aa--py-2 hover:aa--bg-gray-200 [&.active]:aa--border-b-2 [&.active]:aa--border-blue-700 [&.active]:aa--font-semibold [&.active]:aa--text-blue-700"
+                  (click)="onClick(i, subIndex)"
+                >
+                  {{ subItem.title }}
+                </button>
+              </li>
+            </ul>
+          </div>
         </li>
       </ng-template>
     </ng-container>
diff --git a/src/app/shared/ui/header/navbar/navbar.component.ts b/src/app/shared/ui/header/navbar/navbar.component.ts
index 38b0487..484118f 100644
--- a/src/app/shared/ui/header/navbar/navbar.component.ts
+++ b/src/app/shared/ui/header/navbar/navbar.component.ts
@@ -20,7 +20,15 @@ export class NavbarComponent {
     { title: 'Home', href: 'home', active: true },
     { title: 'Find Job', href: '#', active: false },
     { title: 'Employers', href: '#', active: false },
-    { title: 'Candidates', href: 'candidates', active: false },
+    {
+      title: 'Company',
+      active: false,
+      showSubmenu: false,
+      submenu: [
+        { title: 'Create Company', href: 'company/create', active: false },
+        { title: 'Show Companies', href: '#', active: false },
+      ],
+    },
     { title: 'Pricing Plans', href: '#', active: false },
     { title: 'Playground', href: 'playground', active: false },
   ];
@@ -35,6 +43,18 @@ export class NavbarComponent {
     }
   }
 
+  toggleSubmenu(index: number) {
+    this.MenuItems.forEach((item, i) => {
+      item.showSubmenu = i === index;
+    });
+  }
+
+  closeSubmenu(index: number) {
+    setTimeout(() => {
+      this.MenuItems[index].showSubmenu = false;
+    }, 300);
+  }
+
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   trackByTitle(index: number, item: any): string {
     return item.title;
@@ -53,14 +73,29 @@ export class NavbarComponent {
     this.handleMenu();
   }
 
-  onClick(index: number) {
-    for (const item of this.MenuItems) {
-      if (item.active) {
-        item.active = !item.active;
+  onClick(index: number, subIndex?: number) {
+    const menuItem = this.MenuItems[index];
+    if (!menuItem) return;
+
+    this.MenuItems.forEach(item => (item.active = false));
+
+    if (subIndex !== undefined) {
+      if (!menuItem.submenu || !menuItem.submenu[subIndex]) return;
+
+      const submenuItem = menuItem.submenu[subIndex];
+      menuItem.active = true;
+      this.router.navigate([submenuItem.href]);
+      menuItem.showSubmenu = false;
+      return;
+    }
+
+    if (menuItem.submenu && menuItem.submenu.length > 0) {
+      menuItem.showSubmenu = !menuItem.showSubmenu;
+    } else {
+      menuItem.active = true;
+      if (menuItem.href) {
+        this.router.navigate([menuItem.href]);
       }
     }
-    this.MenuItems[index].active = true;
-    const targetHref = this.MenuItems[index].href;
-    this.router.navigate([targetHref]);
   }
 }


Reviews:
user: login: iliasanthimos
body: @Costaknc Congrats  👏 👏 👏  👏 . Your approach is valid an almost correct . I point out some best practices to follow to make the code cleaner and robust.

Full example :

```
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CompanyApiService } from '../../core/config/company-api.service';
import { CompanyModel } from '../../core/models/company.model';
import { NgClass, NgIf } from '@angular/common';

@Component({
  selector: 'aa-company-create',
  templateUrl: './company-create.component.html',
  imports: [ReactiveFormsModule, NgClass, NgIf],
})
export class CompanyCreateComponent implements OnInit {
  newCompany!: FormGroup;
  company!: CompanyModel;

// private fb = inject(FormBuilder);
// private companyApi = inject(CompanyApiService);

  constructor(
    private fb: FormBuilder,
    private companyApi: CompanyApiService
  ) {}

  ngOnInit(): void {
    this.initForm();
  }

  onSubmit(): void {
    if (this.newCompany.invalid) {
      alert('Some fields are not correct! Please check again.');
      return;
    }

    const company: CompanyModel = this.newCompany.value;
    this.companyApi.createCompany(company).subscribe({
      next: response => {
        console.log('Company created successfully:', response);
        alert(`Company "${company.name}" created successfully!`);
        this.newCompany.reset();
      },
      error: error => {
        console.error('Error creating company:', error);
        alert('Failed to create company. Please try again.');
      },
    });
  }

  isFieldInvalid(field: string): boolean {
    const control = this.newCompany.get(field);
    return !!(control && control.invalid && control.touched);
  }

  private initForm(): void {
    this.newCompany = this.fb.group({
      name: ['', Validators.required],
      website: ['', [Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/)]],
      vat: ['', Validators.required],
      logoUrl: [''],
      address: this.fb.group({
        country: [''],
        city: [''],
        street: [''],
        streetNumber: [''],
        postalCode: [''],
      }),
      isSponsored: [false, Validators.required],
      phoneNumber: ['', [Validators.pattern(/^\+?\d{10,15}$/)]],
      openPositions: [0],
      openPositionsUrl: [''],
    });
  }
}

```

- Constructor at the top → Keeps dependencies easy to find.
- Lifecycle hooks (ngOnInit) next → Handles initialization logic.
- Public methods (onSubmit, isFieldInvalid) above private methods → ESLint rule compliance.
- Private methods (initForm) last → Keeps the class structure logical and maintainable.

state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-04T21:30:04Z
user: login: iliasanthimos
body: LGTM 👏 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-05T20:43:54Z

Review comments:
diff_hunk: @@ -0,0 +1,61 @@
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormGroup, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { CompanyApiService } from '../../core/config/company-api.service';
+import { CompanyModel } from '../../core/models/company.model';
+
+@Component({
+  selector: 'aa-company-create',
+  imports: [ReactiveFormsModule, CommonModule],
+  templateUrl: './company-create.component.html',
+})
+export class CompanyCreateComponent {
+  company!: CompanyModel;
+
+  newCompany = new FormGroup({
+    name: new FormControl('', Validators.required),
+    website: new FormControl('', [
+      Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/),
+    ]),
+    vat: new FormControl('', Validators.required),
+    logoUrl: new FormControl(''),
+    address: new FormGroup({
+      country: new FormControl(''),
+      city: new FormControl(''),
+      street: new FormControl(''),
+      streetNumber: new FormControl(''),
+      postalCode: new FormControl(''),
+    }),
+    isSponsored: new FormControl(false, Validators.required),
+    phoneNumber: new FormControl('', [Validators.pattern(/^\+?\d{10,15}$/)]),
+    openPositions: new FormControl(0),
+    openPositionsUrl: new FormControl(''),
+  });
+
+  constructor(private companyApi: CompanyApiService) {}
path: src/app/features/company-create/company-create.component.ts
user: login: iliasanthimos
body: In TypeScript/Angular, the constructor should be placed at the top of the class, right after property declarations and before any lifecycle hooks (ngOnInit, ngAfterViewInit, etc.).

PLUS:  [Angular docs inject](https://angular.dev/api/core/inject) 
[https://dev.to/this-is-angular/always-use-inject-2do4](https://dev.to/this-is-angular/always-use-inject-2do4
)
https://medium.com/netanelbasal/angular-di-using-inject-instead-of-constructor-d0782d434fb1

- Use inject() in components, pipes, and directives → Cleaner code.
- Keep constructor in services (@Injectable()) → inject() doesn’t work in service constructors.
- If using decorators like @Optional(), stick with constructor injection.

```
private fb = inject(FormBuilder);
private companyApi = inject(CompanyApiService);
```


Correct Order:

1. Property declarations
2. Constructor
3. Lifecycle hooks (if present)
4. Methods

 Constructor is at the top, keeping dependencies and initialization easy to find.
 Lifecycle hooks (ngOnInit) follow next, since they handle component logic.
Helper methods (isFieldInvalid, onSubmit) are last, ensuring clean separation.
created_at: 2025-03-04T20:56:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormGroup, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { CompanyApiService } from '../../core/config/company-api.service';
+import { CompanyModel } from '../../core/models/company.model';
+
+@Component({
+  selector: 'aa-company-create',
+  imports: [ReactiveFormsModule, CommonModule],
+  templateUrl: './company-create.component.html',
+})
+export class CompanyCreateComponent {
+  company!: CompanyModel;
+
+  newCompany = new FormGroup({
path: src/app/features/company-create/company-create.component.ts
user: login: iliasanthimos
body: The best practice is to initialize the form inside ngOnInit(), not directly as a class property.

Also Instead of manually nesting new FormGroup({ ... }), it's cleaner to use FormBuilder.



```
newCompany: FormGroup;

  constructor(private fb: FormBuilder, private companyApi: CompanyApiService) 

ngOnInit(): void {
    this.initForm();
  }

  private initForm(): void {
    this.newCompany = this.fb.group({
      name: ['', Validators.required],
      website: ['', [Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/)]],
      vat: ['', Validators.required],
      logoUrl: [''],
      address: this.fb.group({
        country: [''],
        city: [''],
        street: [''],
        streetNumber: [''],
        postalCode: [''],
      }),
      isSponsored: [false, Validators.required],
      phoneNumber: ['', [Validators.pattern(/^\+?\d{10,15}$/)]],
      openPositions: [0],
      openPositionsUrl: [''],
    });
  }
```
created_at: 2025-03-04T20:58:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormGroup, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { CompanyApiService } from '../../core/config/company-api.service';
+import { CompanyModel } from '../../core/models/company.model';
+
+@Component({
+  selector: 'aa-company-create',
+  imports: [ReactiveFormsModule, CommonModule],
+  templateUrl: './company-create.component.html',
+})
+export class CompanyCreateComponent {
+  company!: CompanyModel;
+
+  newCompany = new FormGroup({
+    name: new FormControl('', Validators.required),
+    website: new FormControl('', [
+      Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/),
+    ]),
+    vat: new FormControl('', Validators.required),
+    logoUrl: new FormControl(''),
+    address: new FormGroup({
+      country: new FormControl(''),
+      city: new FormControl(''),
+      street: new FormControl(''),
+      streetNumber: new FormControl(''),
+      postalCode: new FormControl(''),
+    }),
+    isSponsored: new FormControl(false, Validators.required),
+    phoneNumber: new FormControl('', [Validators.pattern(/^\+?\d{10,15}$/)]),
+    openPositions: new FormControl(0),
+    openPositionsUrl: new FormControl(''),
+  });
+
+  constructor(private companyApi: CompanyApiService) {}
+
+  onSubmit() {
+    if (this.newCompany.valid) {
path: src/app/features/company-create/company-create.component.ts
user: login: iliasanthimos
body: Early exit for invalid forms (if (this.newCompany.invalid) return;).
Better readability by avoiding unnecessary nesting.
Minor: Uses company directly instead of assigning it to a separate variable.

```
onSubmit() {
  if (this.newCompany.invalid) {
    alert('Some fields are not correct! Please check again.');
    return;
  }

  const company: CompanyModel = this.newCompany.value;
  this.companyApi.createCompany(company).subscribe({
    next: (response) => {
      console.log('Company created successfully:', response);
      alert(`Company "${company.name}" created successfully!`);
      this.newCompany.reset();
    },
    error: (error) => {
      console.error('Error creating company:', error);
      alert('Failed to create company. Please try again.');
    },
  });
}
```

created_at: 2025-03-04T21:00:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { FormGroup, FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { CompanyApiService } from '../../core/config/company-api.service';
+import { CompanyModel } from '../../core/models/company.model';
+
+@Component({
+  selector: 'aa-company-create',
+  imports: [ReactiveFormsModule, CommonModule],
+  templateUrl: './company-create.component.html',
+})
+export class CompanyCreateComponent {
+  company!: CompanyModel;
+
+  newCompany = new FormGroup({
+    name: new FormControl('', Validators.required),
+    website: new FormControl('', [
+      Validators.pattern(/^(https?:\/\/)?[\w-]+(\.[\w-]+)+[/#?]?.*$/),
+    ]),
+    vat: new FormControl('', Validators.required),
+    logoUrl: new FormControl(''),
+    address: new FormGroup({
+      country: new FormControl(''),
+      city: new FormControl(''),
+      street: new FormControl(''),
+      streetNumber: new FormControl(''),
+      postalCode: new FormControl(''),
+    }),
+    isSponsored: new FormControl(false, Validators.required),
+    phoneNumber: new FormControl('', [Validators.pattern(/^\+?\d{10,15}$/)]),
+    openPositions: new FormControl(0),
+    openPositionsUrl: new FormControl(''),
+  });
+
+  constructor(private companyApi: CompanyApiService) {}
+
+  onSubmit() {
+    if (this.newCompany.valid) {
+      this.company = this.newCompany.value as CompanyModel;
+      console.log('Form Submitted', this.company);
+      this.companyApi.createCompany(this.company).subscribe({
+        next: response => {
+          alert(`Company "${this.company.name}" created successfully!`);
+          console.log('Company created successfully:', response);
+        },
+        error: error => {
+          console.error('Error creating company:', error);
+          alert('Failed to create company. Please try again.');
+        },
+      });
+      this.newCompany.reset();
+    } else {
+      alert('Some fields are not correct! Please check again.');
+    }
+  }
+
+  isFieldInvalid(field: string) {
path: src/app/features/company-create/company-create.component.ts
user: login: iliasanthimos
body: Currently, isFieldInvalid(field) calls .get(field) twice, which is inefficient. Instead:

```
isFieldInvalid(field: string): boolean {
  const control = this.newCompany.get(field);
  return !!(control && control.invalid && control.touched);
}
```

Calls .get(field) only once.
Ensures it always returns a boolean (avoids returning undefined).
created_at: 2025-03-04T21:02:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,201 @@
+<div
+  class="aa--rounded aa--text-start aa--px-8 aa--pt-6 aa--pb-8 aa--mb-4 aa--bg-gray-100 aa--w-full aa--max-w-2xl aa--text-center aa--border aa--rounded aa--border-gray-200 aa--flex-column"
+>
+  <div
+    class="aa--text-black aa--text-2xl aa--align-text-top aa--font-bold aa--origin-center"
+  >
+    Add a new company
+  </div>
+  <form [formGroup]="newCompany" (ngSubmit)="onSubmit()">
+    <div class="aa--mb-4">
+      <label
+        class="aa--block aa--text-gray-700 aa--text-lg aa--font-bold aa--mb-2"
+        for="name"
+      >
+        Name
+      </label>
+      <input
+        class="aa--appearance-none aa--border aa--rounded aa--w-full aa--py-2 aa--px-3 aa--text-gray-700 aa--leading-tight focus:aa--outline-none focus:aa--shadow-outline"
+        id="name"
+        formControlName="name"
+        type="text"
+        placeholder="Company's Name"
+        required
+        [ngClass]="{ 'aa--border-red-500': isFieldInvalid('name') }"
path: src/app/features/company-create/company-create.component.html
user: login: iliasanthimos
body: Minor feel free to ignore me but you can refactor it like [class.aa--border-red-500]="isFieldInvalid('name')"

created_at: 2025-03-04T21:04:42Z
author_association: COLLABORATOR

PR comments:
user: login: Costaknc
created_at: 2025-03-05T09:27:18Z
body: Making the changes and I'll let you know to merge
author_association: COLLABORATOR
user: login: Costaknc
created_at: 2025-03-05T11:56:42Z
body: Corrected all the above comments, one thing though: to implement the "inject()" practice I had to remove the whole constructor (I'm guessing that's how you're supposed to do it at least).
author_association: COLLABORATOR