Skip to main content

Overview

Reviews are an essential part of the Trippins experience, allowing guests to share their experiences and help future travelers make informed decisions. This guide covers how to write, submit, and view reviews for hotel properties.
You must be logged in to write reviews. Anonymous users cannot submit reviews.

Review Data Structure

Each review contains the following information:
interface ReviewDTO {
  reviewId?: number;     // Auto-generated
  rating: number;        // 0-100 (percentage)
  comment: string;       // Review text
  hotelCode: number;     // Property code
  userName: string;      // Reviewer's name
  userDni: string;       // Reviewer's DNI
}

Rating System

Percentage-Based Ratings

Trippins uses a unique 0-100 percentage rating system instead of traditional star ratings:

0-24%

Poor

25-49%

Average

50-74%

Good

75-100%

Excellent

Rating Display Logic

getRatingWidth(rating: number): string {
  return `${rating}%`;
}

getRatingClass(rating: number): string {
  if (rating < 25) {
    return 'poor';
  } else if (rating < 50) {
    return 'average';
  } else if (rating < 75) {
    return 'good';
  } else {
    return 'excellent';
  }
}
This allows for visual representation with color-coded progress bars or stars.

Writing a Review

Review Form Component

@Component({
  selector: 'app-room-details',
  templateUrl: './room-details.component.html'
})
export class RoomDetailsComponent {
  commentForm: FormGroup;
  clientDni: string | null = null;
  clientName: string | null = null;

  constructor(
    private fb: FormBuilder,
    public authService: AuthService,
    private reviewService: ReviewServiceService
  ) {
    this.commentForm = this.fb.group({
      comment: ['', [Validators.required, Validators.minLength(10)]],
      rating: [50, [Validators.required, Validators.min(0), Validators.max(100)]]
    });
  }

  ngOnInit(): void {
    // Get current user information
    this.authService.getUserDni().subscribe(dni => {
      this.clientDni = dni;
    });
    this.authService.getUserName().subscribe(name => {
      this.clientName = name;
    });
  }
}

Form Validation

Minimum 10 characters required to ensure meaningful feedback
Must be between 0 and 100 (inclusive)
Must be logged in with valid JWT token
Must have selected a valid property (hotelCode)

Submitting a Review

1

Navigate to property details

Go to the property page where you want to leave a review
2

Fill review form

Enter your rating (0-100) and comment (min 10 characters)
3

Submit review

Click the submit button to post your review
4

Confirmation

See success message and your review in the list
onSubmitComment(): void {
  // Validate form and authentication
  if (this.commentForm.invalid || !this.house || !this.authService.isLoggedIn()) {
    return;
  }

  // Prepare review data
  const commentData = {
    ...this.commentForm.value,
    hotelCode: this.house.code,
    userDni: this.clientDni,
    userName: this.clientName
  };

  // Submit to API
  this.reviewService.createReview(commentData).subscribe({
    next: () => {
      Swal.fire({
        title: 'Comentario creado de manera exitosa! 🎉',
        text: 'Tu comentario fue creado correctamente.',
        icon: 'success',
        confirmButtonText: '¡Genial!',
        backdrop: true
      });
      
      // Reset form and reload comments
      this.commentForm.reset();
      this.loadComments(this.house!.code, 1);
    },
    error: (err: any) => {
      Swal.fire({
        title: 'Oops 😬',
        text: 'Hubo un error al crear el comentario.',
        icon: 'error',
        confirmButtonText: 'Intentar de nuevo'
      });
      console.error('Error submitting comment:', err);
    }
  });
}

API Endpoints

Create Review

POST /v1/api/reviews

Submit a new review for a property (requires authentication)
rating
number
required
Rating from 0 to 100
comment
string
required
Review text (minimum 10 characters)
hotelCode
number
required
Property code being reviewed
userDni
string
required
Reviewer’s DNI from authenticated session
userName
string
required
Reviewer’s display name
curl -X POST http://localhost:8080/v1/api/reviews \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rating": 85,
    "comment": "Wonderful stay! The ocean view was breathtaking and the staff was incredibly friendly.",
    "hotelCode": 5,
    "userDni": "12345678A",
    "userName": "John Doe"
  }'

Backend Controller

@RestController
@RequestMapping("/v1/api/reviews")
@Tag(name = "Review Management")
public class ReviewRestController {

    @Autowired
    private ReviewService reviewService;

    @Operation(summary = "Create a new review")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "Review created successfully"),
        @ApiResponse(responseCode = "400", description = "Invalid input"),
        @ApiResponse(responseCode = "403", description = "Forbidden - Access denied")
    })
    @SecurityRequirement(name = "JWT")
    @PostMapping
    public ResponseEntity<ReviewDTO> createReview(@RequestBody ReviewDTO review) {
        ReviewDTO createdReview = reviewService.createReview(review);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdReview);
    }
}

Viewing Reviews

Paginated Review Loading

Reviews are loaded with pagination to handle properties with many reviews:
loadComments(code: number, page: number): void {
  this.isLoading = true;
  
  this.reviewService.getPaginatedComments(code, page).subscribe({
    next: (comments: any) => {
      // Append new comments to existing list
      this.comments = [...this.comments, ...comments.content];
      this.currentPage = page;
      this.isLoading = false;
    },
    error: (err: any) => {
      console.error('Error loading comments:', err);
      this.isLoading = false;
    }
  });
}

// Load more comments when user scrolls
loadMoreComments(): void {
  if (this.house) {
    this.loadComments(this.house.code, this.currentPage + 1);
  }
}

Review Display

<div *ngFor="let comment of comments" class="review-card">
  <div class="review-header">
    <span class="reviewer-name">{{ comment.userName }}</span>
    <div class="rating-display" [ngClass]="getRatingClass(comment.rating)">
      <div class="rating-bar" [style.width]="getRatingWidth(comment.rating)"></div>
    </div>
  </div>
  <p class="review-comment">{{ comment.comment }}</p>
</div>

Managing Reviews

Get All Reviews (Admin)

GET /v1/api/reviews

Retrieve all reviews (admin only)
curl http://localhost:8080/v1/api/reviews \
  -H "Authorization: Bearer ADMIN_JWT_TOKEN"
@GetMapping
@SecurityRequirement(name = "JWT")
public ResponseEntity<List<ReviewDTO>> getAllReviews() {
    List<ReviewDTO> reviewDTOs = reviewService.getAllReviews();
    return ResponseEntity.ok(reviewDTOs);
}
Only users with ROLE_ADMIN can access the full list of all reviews across all properties.

Get Specific Review

GET /v1/api/reviews/{id}

Get details of a specific review
curl http://localhost:8080/v1/api/reviews/123 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
@GetMapping("/{id}")
@SecurityRequirement(name = "JWT")
public ResponseEntity<ReviewDTO> getReviewById(@PathVariable Integer id) {
    ReviewDTO review = reviewService.getReviewById(id);
    return ResponseEntity.ok(review);
}

Update Review

PUT /v1/api/reviews/{id}

Modify an existing review
curl -X PUT http://localhost:8080/v1/api/reviews/123 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rating": 90,
    "comment": "Updated: Even better than I initially thought!",
    "hotelCode": 5,
    "userDni": "12345678A",
    "userName": "John Doe"
  }'
Consider implementing business rules such as:
  • Users can only edit their own reviews
  • Reviews can only be edited within 24 hours of posting
  • Show “edited” indicator on modified reviews

Delete Review

DELETE /v1/api/reviews/{id}

Remove a review (admin only)
curl -X DELETE http://localhost:8080/v1/api/reviews/123 \
  -H "Authorization: Bearer ADMIN_JWT_TOKEN"
@DeleteMapping("/{id}")
@SecurityRequirement(name = "JWT") 
public ResponseEntity<Void> deleteReview(@PathVariable Integer id) {
    reviewService.deleteReview(id);
    return ResponseEntity.noContent().build();
}

Review Entity Relationships

The review entity connects users and housing properties:
public class Review {
    private Integer reviewId;
    private Integer rating;
    private String comment;
    
    // Foreign key relationships
    private Housing hotel;    // Link to Housing entity
    private User user;        // Link to User entity
}
The DTO simplifies this by using:
  • hotelCode instead of full Housing object
  • userDni and userName instead of full User object

Integration with Reservations

Linking Reviews to Stays

Best practice: Only allow reviews from users who have stayed at the property
// Check if user has a completed reservation
canWriteReview(userDni: string, hotelCode: number): Observable<boolean> {
  return this.reservationService.getUserReservations(userDni).pipe(
    map(reservations => {
      return reservations.some(res => 
        res.housingCode === hotelCode && 
        new Date(res.checkOut) < new Date() &&
        !res.valorated  // Not yet reviewed
      );
    })
  );
}

Updating Reservation After Review

When a user submits a review, mark the reservation as reviewed:
onSubmitComment(): void {
  const reviewData = { /* ... */ };
  
  this.reviewService.createReview(reviewData).subscribe({
    next: () => {
      // Mark reservation as valorated
      if (this.currentReservationId) {
        this.reservationService.updateReservation(this.currentReservationId, {
          ...this.reservation,
          valorated: true
        }).subscribe();
      }
      
      // Show success message and reload
      this.showSuccessMessage();
      this.loadComments(this.house!.code, 1);
    }
  });
}

Best Practices

Implement profanity filtering and spam detection:
validateReviewContent(comment: string): boolean {
  const bannedWords = ['spam', 'inappropriate'];
  return !bannedWords.some(word => comment.toLowerCase().includes(word));
}
Prevent review spam by limiting submissions:
  • One review per property per user
  • Cooldown period between reviews
  • Verify completed reservation before allowing review
Add voting system for helpful reviews:
interface Review {
  // ... existing fields
  helpfulCount: number;
  notHelpfulCount: number;
}
Allow property owners to respond to reviews to improve engagement and address concerns.

UI/UX Recommendations

Review Form Best Practices

1

Visual rating input

Use slider or star selector for intuitive rating input
2

Character counter

Show real-time character count (min 10 required)
3

Preview

Let users preview their review before submitting
4

Guidelines

Display review guidelines and terms of service

Review Display

<div class="review-list">
  <!-- Sort options -->
  <select [(ngModel)]="sortBy" (change)="sortReviews()">
    <option value="newest">Newest First</option>
    <option value="highest">Highest Rating</option>
    <option value="lowest">Lowest Rating</option>
  </select>

  <!-- Review cards -->
  <div *ngFor="let review of comments" class="review-card">
    <div class="review-header">
      <span class="reviewer">{{ review.userName }}</span>
      <span class="rating" [ngClass]="getRatingClass(review.rating)">
        {{ review.rating }}%
      </span>
    </div>
    <p class="comment">{{ review.comment }}</p>
  </div>

  <!-- Load more button -->
  <button *ngIf="hasMoreReviews" (click)="loadMoreComments()">
    Load More Reviews
  </button>
</div>

Analytics and Insights

Calculate Average Rating

calculateAverageRating(reviews: ReviewDTO[]): number {
  if (reviews.length === 0) return 0;
  
  const sum = reviews.reduce((acc, review) => acc + review.rating, 0);
  return Math.round(sum / reviews.length);
}

Rating Distribution

getRatingDistribution(reviews: ReviewDTO[]): RatingDistribution {
  return {
    excellent: reviews.filter(r => r.rating >= 75).length,
    good: reviews.filter(r => r.rating >= 50 && r.rating < 75).length,
    average: reviews.filter(r => r.rating >= 25 && r.rating < 50).length,
    poor: reviews.filter(r => r.rating < 25).length
  };
}

Error Handling

User not logged in - redirect to login page
  • Comment too short (< 10 characters)
  • Invalid rating (outside 0-100 range)
  • Missing required fields
User trying to edit/delete another user’s review
Property doesn’t exist or review ID invalid
User already reviewed this property (if enforcing one review per property)

Making Reservations

Book a stay before writing a review

Authentication

Log in to submit reviews

Browsing Housing

Find properties to review

User Roles

Understand admin review management permissions

Build docs developers (and LLMs) love