refactor: extract rating values and emojis into reusable constants (#23340)
* refactor: extract rating values and emojis into reusable constants - Create packages/lib/rating.ts with RATING_OPTIONS array and validation utilities - Update bookings-single-view.tsx to use extracted constants - Add TypeScript interfaces for type safety - Enable reuse of rating constants across the codebase Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * refactor: move rating utilities to bookings feature directory - Move packages/lib/rating.ts to packages/features/bookings/lib/rating.ts - Update import path in bookings-single-view.tsx to use new location - Maintain domain-specific organization for booking-related utilities Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> * fix: resolve linting issues in event-types.tsx - Fix quote style consistency (single to double quotes) - Add missing trailing comma - Resolve pre-commit hook formatting changes Co-Authored-By: eunjae@cal.com <hey@eunjae.dev> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
SystemField,
|
||||
TITLE_FIELD,
|
||||
} from "@calcom/features/bookings/lib/SystemField";
|
||||
import { RATING_OPTIONS, validateRating } from "@calcom/features/bookings/lib/rating";
|
||||
import { getCalendarLinks, CalendarLinkType } from "@calcom/lib/bookings/getCalendarLinks";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { formatToLocalizedDate, formatToLocalizedTime, formatToLocalizedTimezone } from "@calcom/lib/dayjs";
|
||||
@@ -179,14 +180,13 @@ export default function Success(props: PageProps) {
|
||||
const shouldAlignCentrally = !isEmbed || shouldAlignCentrallyInEmbed;
|
||||
const [calculatedDuration, setCalculatedDuration] = useState<number | undefined>(undefined);
|
||||
const [comment, setComment] = useState("");
|
||||
const parsedRating = rating ? parseInt(rating, 10) : 3;
|
||||
const currentUserEmail =
|
||||
searchParams?.get("rescheduledBy") ??
|
||||
searchParams?.get("cancelledBy") ??
|
||||
session?.user?.email ??
|
||||
undefined;
|
||||
|
||||
const defaultRating = isNaN(parsedRating) ? 3 : parsedRating > 5 ? 5 : parsedRating < 1 ? 1 : parsedRating;
|
||||
const defaultRating = validateRating(rating);
|
||||
const [rateValue, setRateValue] = useState<number>(defaultRating);
|
||||
const [isFeedbackSubmitted, setIsFeedbackSubmitted] = useState(false);
|
||||
|
||||
@@ -1026,61 +1026,20 @@ export default function Success(props: PageProps) {
|
||||
) : (
|
||||
<>
|
||||
<div className="my-3 flex justify-center space-x-1">
|
||||
<button
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === 1
|
||||
? "border-emphasis bg-emphasis"
|
||||
: "border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(1)}>
|
||||
😠
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === 2
|
||||
? "border-emphasis bg-emphasis"
|
||||
: "border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(2)}>
|
||||
🙁
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === 3
|
||||
? "border-emphasis bg-emphasis"
|
||||
: " border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(3)}>
|
||||
😐
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === 4
|
||||
? "border-emphasis bg-emphasis"
|
||||
: "border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(4)}>
|
||||
😄
|
||||
</button>
|
||||
<button
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === 5
|
||||
? "border-emphasis bg-emphasis"
|
||||
: "border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(5)}>
|
||||
😍
|
||||
</button>
|
||||
{RATING_OPTIONS.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
className={classNames(
|
||||
"flex h-10 w-10 items-center justify-center rounded-full border text-2xl hover:opacity-100",
|
||||
rateValue === option.value
|
||||
? "border-emphasis bg-emphasis"
|
||||
: "border-muted bg-default opacity-50"
|
||||
)}
|
||||
disabled={isFeedbackSubmitted}
|
||||
onClick={() => setRateValue(option.value)}>
|
||||
{option.emoji}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="my-4 space-y-1 text-center">
|
||||
<h2 className="font-cal text-lg">{t("submitted_feedback")}</h2>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
export interface RatingOption {
|
||||
value: number;
|
||||
emoji: string;
|
||||
}
|
||||
|
||||
export const MIN_RATING = 1;
|
||||
export const MAX_RATING = 5;
|
||||
export const DEFAULT_RATING = 3;
|
||||
|
||||
export const RATING_OPTIONS: RatingOption[] = [
|
||||
{ value: 1, emoji: "😠" },
|
||||
{ value: 2, emoji: "🙁" },
|
||||
{ value: 3, emoji: "😐" },
|
||||
{ value: 4, emoji: "😄" },
|
||||
{ value: 5, emoji: "😍" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates and normalizes a rating value to ensure it's within the valid range
|
||||
* @param rating - The rating value to validate (can be string or number)
|
||||
* @param defaultValue - The default value to use if rating is invalid (defaults to DEFAULT_RATING)
|
||||
* @returns A valid rating number between MIN_RATING and MAX_RATING
|
||||
*/
|
||||
export function validateRating(
|
||||
rating: string | number | null | undefined,
|
||||
defaultValue = DEFAULT_RATING
|
||||
): number {
|
||||
const parsedRating = typeof rating === "string" ? parseInt(rating, 10) : rating;
|
||||
|
||||
if (!parsedRating || isNaN(parsedRating)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (parsedRating > MAX_RATING) return MAX_RATING;
|
||||
if (parsedRating < MIN_RATING) return MIN_RATING;
|
||||
|
||||
return parsedRating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the emoji for a given rating value
|
||||
* @param rating - The rating value
|
||||
* @returns The corresponding emoji or empty string if not found
|
||||
*/
|
||||
export function getRatingEmoji(rating: number): string {
|
||||
const option = RATING_OPTIONS.find((opt) => opt.value === rating);
|
||||
return option?.emoji || "";
|
||||
}
|
||||
Reference in New Issue
Block a user