Skip to main content
BarberApp includes a rating system that allows clients to provide feedback on completed appointments. This guide explains how ratings work, how to submit them, and where they appear throughout the application.

Understanding the rating system

Rating structure

Ratings in BarberApp consist of:

Score

  • Required field
  • 1 to 5 stars
  • Integer values only
  • Visual star selection interface

Comment

  • Optional field
  • Maximum 145 characters
  • Private feedback
  • Not visible to specialists
Data model (rating.model.ts):
interface Rating {
  score: AllowedScore;      // 1 | 2 | 3 | 4 | 5
  comment?: string;         // Optional, max 145 chars
}

When ratings are available

Clients can rate an appointment when: Conditions met:
  • Appointment status is “completed”
  • Client is viewing their own appointment
  • Appointment has not been rated yet
Cannot rate when:
  • Appointment is still pending
  • Appointment was canceled
  • Appointment already has a rating
  • Viewing as a specialist
Logic implementation (appointment-actions.component.ts:249-258):
if (userRoleToShow === 'client' && !this.appointment.rating) {
  baseActions.push({
    handler: this.rateAppointmentHandler,
    label: 'Calificar Atención',
    icon: 'star',
    // ... styling
  });
}

Rating an appointment

Accessing the rating dialog

1

Navigate to completed appointment

Go to your appointments list and find a completed appointment.
2

Click 'Calificar Atención'

Click the yellow “Calificar Atención” (Rate Service) button.
This button only appears for completed appointments that haven’t been rated yet.
3

Rating dialog opens

A modal dialog appears with the rating interface.
Component: RateAppointmentComponent (appointment-rate.component.ts:47)

The rating dialog

The dialog displays: Header section:
  • Icon: Star icon in colored background
  • Title: “Calificar Atención” (Rate Service)
  • Message: “Valoramos tu sinceridad. Esta información no será visible para el especialista.”
Form fields:
  • Star rating selector (required)
  • Comment text area (optional)
Actions:
  • “Cancelar” (Cancel) - Close without saving
  • “Confirmar” (Confirm) - Submit rating (disabled until valid)
Dialog handler (appointment-actions.component.ts:151-165):
rateAppointmentHandler = (): void => {
  this.dialogService
    .openGeneric<RateAppointmentComponent, RateAppointmentData>(
      RateAppointmentComponent,
      {
        title: 'Calificar Atención',
        message: 'Valoramos tu sinceridad. Esta información no será visible para el especialista.',
      }
    )
    .subscribe((result) => {
      if (result) {
        this.rateAppointment.emit(result);
      }
    });
};

Selecting a star rating

Interactive star selector

The rating interface includes 5 interactive stars:
1

Hover over stars

  • Stars light up in yellow (text-yellow-400) as you hover
  • All stars up to the hovered position highlight
  • Provides visual feedback before selection
Handler: hoverStar() (appointment-rate.component.ts:106-109)
2

Click to select

  • Click a star to lock in your rating
  • Selected stars remain filled
  • Unselected stars remain outlined
  • Updates form value immediately
Handler: selectStar() (appointment-rate.component.ts:100-104)
3

Move mouse away

  • Hover highlight resets when mouse leaves star area
  • Selected stars remain visible
Handler: resetHover() (appointment-rate.component.ts:111-114)

Star display logic

The stars use conditional rendering (appointment-rate.component.html:44-48):
<app-svg-icon
  [icon]="selectedStar >= star ? 'starFill' : 'star'"
  [class]="(hoveredStar || selectedStar) >= star ? 'text-yellow-400' : 'text-gray-300'"
  size="56px" <!-- Desktop: 56px, Mobile: 40px -->
></app-svg-icon>
Icon variants:
  • starFill - Solid filled star (when selected)
  • star - Outlined star (when not selected)
Colors:
  • Yellow (text-yellow-400) - Hovered or selected
  • Gray (text-gray-300) - Not hovered or selected

Responsive star sizes

Stars adapt to screen size:
  • Desktop (xs:block): 56px icons
  • Mobile (xs:hidden): 40px icons
Implementation (appointment-rate.component.html:43-56)

Adding a comment

Comment field details

comment
string
Comentario (Comment)
  • Label: “Comentario (Opcional)” (Comment - Optional)
  • Placeholder: “Contanos tu experiencia.” (Tell us about your experience)
  • Maximum length: 145 characters
  • Character counter displayed
  • Rows: 2 (auto-expands with field-sizing-content)
  • Not required for submission
Privacy notice: The dialog explicitly states ratings are not visible to specialists (appointment-rate.component.html:24).

Character counter

A real-time counter shows character usage (appointment-rate.component.html:81-83):
<div class="text-xs text-gray-500 text-right">
  {{ form.value['comment'].length }}/145
</div>

Form validation

Validation rules

The rating form validates:
score
AllowedScore
required
  • Required: Must select a star rating
  • Range: 1-5 (integer values)
  • Validators: Validators.required, Validators.min(1), Validators.max(5)
comment
string
  • Optional: Can be left empty
  • Max length: 145 characters (enforced by textarea maxLength attribute)
  • Processing: Empty comments are stored as undefined
Form setup (appointment-rate.component.ts:62-66):
this.form = this.fb.group({
  score: ['', [Validators.required, Validators.min(1), Validators.max(5)]],
  comment: [''],
});

Submit button behavior

The “Confirmar” (Confirm) button is: Enabled when:
  • A star rating has been selected (1-5)
  • Form is valid
Disabled when:
  • No star rating selected
  • Form validation fails
Button implementation (appointment-rate.component.html:96-109):
<button
  type="submit"
  [disabled]="form.invalid"
  class="... disabled:opacity-50 disabled:cursor-auto"
>
  {{ config.confirmText || "Confirmar" }}
</button>

Submitting a rating

Submission process

1

Form validation

System checks that a star rating is selected.
2

Data processing

Empty comment field is converted to undefined:
if (formValue.comment === '') formValue.comment = undefined;
Code: appointment-rate.component.ts:92
3

Dialog closes

  • Dialog plays closing animation (85ms)
  • Form data is emitted to parent component
  • Parent saves rating to appointment
4

Rating is saved

  • Rating is permanently attached to the appointment
  • “Calificar Atención” button disappears
  • Rating becomes visible in appointment details
Close handler (appointment-rate.component.ts:85-98):
onClose(result: RateAppointmentData | null): void {
  this.dialog.nativeElement.classList.add('closing');
  
  setTimeout(() => {
    this.dialog.nativeElement.close();
    if (result && this.form.valid) {
      const formValue = this.form.value;
      if (formValue.comment === '') formValue.comment = undefined;
      this.closed.emit(formValue);
    } else {
      this.closed.emit(null);
    }
  }, 85); // Closing animation duration
}

Where ratings appear

In appointment details

Once rated, appointments display the rating in the Appointment Info component: Display format:
  • Label: “Calificación de la Atención” (Service Rating)
  • Visual: Star icons (filled/unfilled based on score)
  • Text: Score out of 5 (e.g., “(4/5)”)
Implementation (appointment-info.component.html:61-72):
@if (rating) {
  <div class="flex flex-col">
    <span class="text-sm font-medium text-[#221510]/60">Calificación de la Atención</span>
    <div class="flex items-center">
      @for (star of stars; track star) {
        <app-svg-icon 
          [icon]="rating.score >= star ? 'starFill' : 'star'" 
          class="text-yellow-400"
          size="20px"
        ></app-svg-icon>
      }
      <span class="ml-2 font-semibold text-gray-600">({{rating.score}}/5)</span>
    </div>
  </div>
}

Star rendering in details

The appointment info shows:
  • 5 stars total (stars array: [1, 2, 3, 4, 5])
  • Filled stars: Based on rating score (e.g., 4 filled for 4/5 rating)
  • Size: 20px per star (smaller than rating dialog)
  • Color: Yellow (text-yellow-400)

Rating visibility

  • Own ratings on their appointment details
  • Stars and score display
  • Comment is stored but not displayed in UI
Privacy protection: The rating dialog explicitly states: “Esta información no será visible para el especialista” (This information will not be visible to the specialist).

Rating data storage

Ratings are stored as part of the Appointment model: Data structure (appointment.model.ts:19):
export interface Appointment {
  id: string;
  // ... other fields
  rating?: Rating;  // Optional - only present after client rates
}
Storage behavior:
  • Initially undefined for new appointments
  • Set once when client submits rating
  • Immutable after submission (no editing)
  • Persisted in Firebase with appointment record

Dialog lifecycle

Opening animation

When the rating dialog opens (appointment-rate.component.ts:69-78):
  1. Dialog element is shown via showModal()
  2. Close event listener is attached
  3. Layout is forced (offsetHeight read)
  4. showing class added via requestAnimationFrame
  5. CSS transitions animate the dialog in

Closing animation

When closing (appointment-rate.component.ts:85-98):
  1. closing class is added
  2. 85ms timeout allows CSS animation
  3. Dialog closes programmatically
  4. Result is emitted (rating data or null)

Cleanup

On component destruction (appointment-rate.component.ts:81-83):
ngOnDestroy(): void {
  this.dialog.nativeElement.removeEventListener('close', this.handleClose);
}

Best practices

For clients

  • Rate appointments after service completion
  • Provide honest, constructive feedback
  • Use comments to elaborate on your experience
  • Remember: specialists cannot see your rating

For developers

  • Always check appointment.rating existence before displaying
  • Validate rating score is within 1-5 range
  • Handle optional comment field properly
  • Respect privacy settings (no specialist access)

Common scenarios

No, ratings are immutable once submitted. This ensures data integrity and prevents manipulation. If you want to change your rating, contact BarberApp support.
No rating is saved. The “Calificar Atención” button remains available, and you can rate the appointment later.
No. All ratings are anonymous from the specialist’s perspective. They cannot see individual ratings or comments.
Currently, ratings are collected but not used for public ranking. Future features may include aggregated statistics visible to specialists.
Yes, as long as the appointment is completed and hasn’t been rated yet. There’s no time limit for submitting ratings.

Technical implementation details

Component architecture

The rating system uses:
  • Dialog component: RateAppointmentComponent (Angular standalone component)
  • Form management: Angular Reactive Forms with FormBuilder
  • State management: Angular signals for hoveredStar and selectedStar
  • Change detection: OnPush strategy for performance
  • Dialog service: Centralized dialog management
Configuration interface (appointment-rate.component.ts:26-38):
export interface RateAppointmentDialogConfig {
  title: string;
  message: string;
  confirmText?: string;
  confirmTextColor?: string;
  confirmTextBgColor?: string;
  // ... more styling options
}

Event flow

  1. Button clickrateAppointmentHandler() called
  2. Dialog opensRateAppointmentComponent instantiated
  3. User interacts → Star selection and comment input
  4. Form submit → Validation and data processing
  5. Dialog closesclosed EventEmitter fires
  6. Parent receivesrateAppointment EventEmitter in parent
  7. Data persists → Rating saved to appointment in Firebase

Build docs developers (and LLMs) love