Skip to content

Commit e4d29da

Browse files
authored
Merge branch 'main' into home-page-redesign-backend
2 parents a244f72 + d725bd2 commit e4d29da

File tree

14 files changed

+161
-335
lines changed

14 files changed

+161
-335
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ on its own, run `yarn frontend-dev` or `yarn backend-dev`.
2727
### 2024-2025
2828

2929
- **Ella Krechmer** - Product Manager
30+
- **Jaeyoung Lee** - Product Manager
3031
- **Janet Luo** - Associate Product Manager
32+
- **Celline Lee** - Associate Product Manager
3133
- **Jacob Green** - Product Marketing Manager
34+
- **Arsh Aggarwal** - Product Marketing Manager
35+
- **Jasmine Ren** - Product Marketing Manager
3236
- **Gunyasorn (Grace) Sawatyanon** - Technical Product Manager
3337
- **David Martinez Lopez** - Designer
3438
- **Vicky Wang** - Designer
39+
- **Erica Lee** - Designer
40+
- **May Wu** - Designer
3541
- **Kea-Roy Ong** - Developer
3642
- **Casper Liao** - Developer
3743
- **Parsa Tehranipoor** - Developer

backend/scripts/add_reviews.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ const formatReview = (data: any): Review => ({
2424
detailedRatings: {
2525
location: data['detailedRatings.location'],
2626
safety: data['detailedRatings.safety'],
27-
value: data['detailedRatings.value'],
2827
maintenance: data['detailedRatings.maintenance'],
29-
communication: data['detailedRatings.communication'],
3028
conditions: data['detailedRatings.condition'],
3129
},
3230
date: data.date,

backend/scripts/add_reviews_nodups.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ const formatReview = (data: any): Review => ({
2424
detailedRatings: {
2525
location: data['detailedRatings.location'],
2626
safety: data['detailedRatings.safety'],
27-
value: data['detailedRatings.value'],
2827
maintenance: data['detailedRatings.maintenance'],
29-
communication: data['detailedRatings.communication'],
3028
conditions: data['detailedRatings.condition'],
3129
},
3230
date: data.date,

backend/src/data/new_reviews.json

Lines changed: 0 additions & 169 deletions
Large diffs are not rendered by default.

backend/src/data/reviews.json

Lines changed: 0 additions & 110 deletions
Large diffs are not rendered by default.

common/types/db-types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ type StringSet = Record<string, boolean>;
77
export type DetailedRating = {
88
readonly location: number;
99
readonly safety: number;
10-
readonly value: number;
1110
readonly maintenance: number;
12-
readonly communication: number;
1311
readonly conditions: number;
1412
};
1513

frontend/src/components/Admin/AdminReview.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,7 @@ const AdminReviewComponent = ({
152152
return [
153153
{ feature: 'Location', rating: ratings.location },
154154
{ feature: 'Safety', rating: ratings.safety },
155-
{ feature: 'Value', rating: ratings.value },
156155
{ feature: 'Maintenance', rating: ratings.maintenance },
157-
{ feature: 'Communication', rating: ratings.communication },
158156
{ feature: 'Conditions', rating: ratings.conditions },
159157
];
160158
};

frontend/src/components/ApartmentCard/ApartmentCard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ const ApartmentCard = ({
103103
const img = photos.length > 0 ? photos[0] : ApartmentImg;
104104
const isMobile = useMediaQuery('(max-width:600px)');
105105
const [reviewList, setReviewList] = useState<ReviewWithId[]>([]);
106-
const sampleReview = reviewList.length === 0 ? '' : reviewList[0].reviewText;
106+
const sampleReview =
107+
reviewList.length === 0
108+
? ''
109+
: reviewList.sort((a, b) => {
110+
return (b.likes ?? 0) - (a.likes ?? 0);
111+
})[0].reviewText;
107112
const [isSaved, setIsSaved] = useState(false);
108113
const [isHovered, setIsHovered] = useState(false);
109114
const [savedIsHovered, setSavedIsHovered] = useState(false);

frontend/src/components/LeaveReview/ReviewModal.tsx

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,14 @@ interface FormData {
123123
}
124124

125125
const defaultReview: FormData = {
126-
bedrooms: 0,
127-
price: 0,
126+
bedrooms: -1,
127+
price: -1,
128128
overallRating: 0,
129129
address: '',
130130
ratings: {
131131
location: 0,
132132
safety: 0,
133-
value: 0,
134133
maintenance: 0,
135-
communication: 0,
136134
conditions: 0,
137135
},
138136
localPhotos: [],
@@ -177,8 +175,7 @@ const reducer = (state: FormData, action: Action): FormData => {
177175
*
178176
* This component displays a modal for users to input information for their review about a specific apartment.
179177
* This includes the bedroom(s), price per person, overall rating, detailed ratings (location, safety, maintenance,
180-
* conditions), review text/body, pictures (up to 3 pictures). The information that is required are: overall experience
181-
* and review text/body, all other information are optional.
178+
* conditions), review text/body, pictures (up to 3 pictures). All fields are required.
182179
* The submit button will add the review to the database and set the status as PENDING until an admin approves it.
183180
* The modal is responsive for all screen sizes and mobile display.
184181
*
@@ -217,9 +214,7 @@ const ReviewModal = ({
217214
ratings: {
218215
location: review.detailedRatings.location,
219216
safety: review.detailedRatings.safety,
220-
value: review.detailedRatings.value,
221217
maintenance: review.detailedRatings.maintenance,
222-
communication: review.detailedRatings.communication,
223218
conditions: review.detailedRatings.conditions,
224219
},
225220
localPhotos: review.photos.map((photo) => new File([], photo)),
@@ -235,6 +230,12 @@ const ReviewModal = ({
235230
const [showError, setShowError] = useState(false);
236231
const [emptyTextError, setEmptyTextError] = useState(false);
237232
const [ratingError, setRatingError] = useState(false);
233+
const [bedroomError, setBedroomError] = useState(false);
234+
const [priceError, setPriceError] = useState(false);
235+
const [locationError, setLocationError] = useState(false);
236+
const [maintenanceError, setMaintenanceError] = useState(false);
237+
const [safetyError, setSafetyError] = useState(false);
238+
const [conditionsError, setConditionsError] = useState(false);
238239
const [includesProfanityError, setIncludesProfanityError] = useState(false);
239240
const [addedPhoto, setAddedPhoto] = useState(false);
240241
const modalRef = useRef<HTMLDivElement>(null);
@@ -332,10 +333,26 @@ const ReviewModal = ({
332333
if (
333334
data.reviewText === '' ||
334335
data.overallRating === 0 ||
336+
data.price < 0 ||
337+
data.bedrooms < 0 ||
338+
data.detailedRatings.location === 0 ||
339+
data.detailedRatings.conditions === 0 ||
340+
data.detailedRatings.maintenance === 0 ||
341+
data.detailedRatings.safety === 0 ||
335342
includesProfanity(data.reviewText)
336343
) {
337344
data.overallRating === 0 ? setRatingError(true) : setRatingError(false);
338-
data.reviewText === '' ? setEmptyTextError(true) : setEmptyTextError(false);
345+
data.reviewText.length < 15 ? setEmptyTextError(true) : setEmptyTextError(false);
346+
data.price < 0 ? setPriceError(true) : setPriceError(false);
347+
data.bedrooms < 0 ? setBedroomError(true) : setBedroomError(false);
348+
data.detailedRatings.conditions === 0
349+
? setConditionsError(true)
350+
: setConditionsError(false);
351+
data.detailedRatings.location === 0 ? setLocationError(true) : setLocationError(false);
352+
data.detailedRatings.maintenance === 0
353+
? setMaintenanceError(true)
354+
: setMaintenanceError(false);
355+
data.detailedRatings.safety === 0 ? setSafetyError(true) : setSafetyError(false);
339356
includesProfanity(data.reviewText)
340357
? setIncludesProfanityError(true)
341358
: setIncludesProfanityError(false);
@@ -356,7 +373,8 @@ const ReviewModal = ({
356373
if (!initialReview) {
357374
dispatch({ type: 'reset' });
358375
}
359-
onSuccess();
376+
sessionStorage.setItem('showModifiedReviewSuccessToast', 'true');
377+
window.location.reload();
360378
} catch (err) {
361379
console.log(err);
362380
console.log('Failed to submit form');
@@ -374,6 +392,18 @@ const ReviewModal = ({
374392
onClose();
375393
};
376394

395+
const resetErrors = () => {
396+
setEmptyTextError(false);
397+
setRatingError(false);
398+
setBedroomError(false);
399+
setPriceError(false);
400+
setPriceError(false);
401+
setLocationError(false);
402+
setMaintenanceError(false);
403+
setConditionsError(false);
404+
setSafetyError(false);
405+
};
406+
377407
const removePhoto = (index: number) => {
378408
const newPhotos = review.localPhotos.filter((_, photoIndex) => index !== photoIndex);
379409
dispatch({ type: 'updatePhotos', photos: newPhotos });
@@ -391,6 +421,13 @@ const ReviewModal = ({
391421
return () => clearTimeout(timer);
392422
}, [addedPhoto]);
393423

424+
useEffect(() => {
425+
if (sessionStorage.getItem('showModifiedReviewSuccessToast') === 'true') {
426+
onSuccess(); // Call the toast notification function
427+
sessionStorage.removeItem('showModifiedReviewSuccessToast'); // Clean up so it doesn't trigger again
428+
}
429+
}, []);
430+
394431
/**
395432
* Returns the "Things to consider in your review:" prompt box. Function serves to help mobile display.
396433
*/
@@ -455,7 +492,7 @@ const ReviewModal = ({
455492
justifyContent="flex-start"
456493
>
457494
<Grid item style={{ marginRight: '10px', paddingLeft: '0' }}>
458-
<Typography>Bedroom(s)</Typography>
495+
<Typography color={!bedroomError ? 'initial' : 'error'}>Bedroom(s) *</Typography>
459496
</Grid>
460497
<Grid
461498
item
@@ -477,6 +514,11 @@ const ReviewModal = ({
477514
/>
478515
<ExpandMoreIcon className={expandMoreIcon} />
479516
</Grid>
517+
{bedroomError && (
518+
<Typography color="error" style={{ fontSize: '12px', minWidth: '200px' }}>
519+
* Required
520+
</Typography>
521+
)}
480522
</Grid>
481523

482524
<Grid
@@ -495,7 +537,9 @@ const ReviewModal = ({
495537
!isMobile ? { marginRight: '10px', marginLeft: 'auto' } : { marginRight: '10px' }
496538
}
497539
>
498-
<Typography>Price Per Person</Typography>
540+
<Typography color={!priceError ? 'initial' : 'error'}>
541+
Price Per Person *
542+
</Typography>
499543
</Grid>
500544
<Grid
501545
item
@@ -513,18 +557,34 @@ const ReviewModal = ({
513557
/>
514558
<ExpandMoreIcon className={expandMoreIcon} />
515559
</Grid>
560+
{priceError && (
561+
<Typography
562+
color="error"
563+
style={{ fontSize: '12px', justifyItems: 'flex-start', minWidth: '180px' }}
564+
>
565+
* Required
566+
</Typography>
567+
)}
516568
</Grid>
517569
</Grid>
518570
<Grid container direction="column" justifyContent="space-evenly" spacing={4}>
519-
<Grid container item>
520-
<ReviewRating
521-
name="overall"
522-
label="Overall Experience"
523-
onChange={updateOverall()}
524-
defaultValue={initialReview?.overallRating || 0}
525-
></ReviewRating>
526-
{ratingError && <Typography color="error">*This field is required</Typography>}
571+
<Grid container item xs={12}>
572+
<Grid container item>
573+
<ReviewRating
574+
name="overall"
575+
label="Overall Experience *"
576+
onChange={updateOverall()}
577+
defaultValue={initialReview?.overallRating || 0}
578+
error={ratingError}
579+
></ReviewRating>
580+
</Grid>
581+
{ratingError && (
582+
<Typography color="error" style={{ fontSize: '12px' }}>
583+
* Required
584+
</Typography>
585+
)}
527586
</Grid>
587+
528588
<div className={styles.div}></div>
529589
{/* <Grid container item justifyContent="space-between" xs={12} sm={6}>
530590
<TextField
@@ -539,29 +599,38 @@ const ReviewModal = ({
539599
<Grid container spacing={1} justifyContent="center">
540600
<ReviewRating
541601
name="location"
542-
label="Location"
602+
label="Location *"
543603
onChange={updateRating('location')}
544604
defaultValue={initialReview?.detailedRatings.location || 0}
605+
error={locationError}
545606
></ReviewRating>
546607
<ReviewRating
547608
name="safety"
548-
label="Safety"
609+
label="Safety *"
549610
onChange={updateRating('safety')}
550611
defaultValue={initialReview?.detailedRatings.safety || 0}
612+
error={safetyError}
551613
></ReviewRating>
552614
<ReviewRating
553615
name="maintenance"
554-
label="Maintenance"
616+
label="Maintenance *"
555617
onChange={updateRating('maintenance')}
556618
defaultValue={initialReview?.detailedRatings.maintenance || 0}
619+
error={maintenanceError}
557620
></ReviewRating>
558621
<ReviewRating
559622
name="conditions"
560-
label="Conditions"
623+
label="Conditions *"
561624
onChange={updateRating('conditions')}
562625
defaultValue={initialReview?.detailedRatings.conditions || 0}
626+
error={conditionsError}
563627
></ReviewRating>
564628
</Grid>
629+
{(conditionsError || safetyError || maintenanceError || locationError) && (
630+
<Typography color="error" style={{ fontSize: '12px', marginTop: '5px' }}>
631+
* These fields are required
632+
</Typography>
633+
)}
565634
</Grid>
566635

567636
<div className={styles.div}></div>
@@ -597,7 +666,7 @@ const ReviewModal = ({
597666
}}
598667
placeholder="Write your review here"
599668
helperText={`${review.body.length}/${REVIEW_CHARACTER_LIMIT}${
600-
emptyTextError ? ' This field is required' : ''
669+
emptyTextError ? ' A minimum of 15 characters is required' : ''
601670
}${
602671
includesProfanityError
603672
? ' This review contains profanity. Please edit it and try again.'
@@ -640,7 +709,10 @@ const ReviewModal = ({
640709
<Button
641710
variant="contained"
642711
disableElevation
643-
onClick={initialReview ? onClose : onCloseClearPhotos}
712+
onClick={() => {
713+
(initialReview ? onClose : onCloseClearPhotos)();
714+
resetErrors();
715+
}}
644716
className={hollowRedButton}
645717
style={{ marginLeft: '15px' }}
646718
>

frontend/src/components/LeaveReview/ReviewRating.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface Props {
77
label: string;
88
onChange: (event: React.ChangeEvent<{}>, value: number | null) => void;
99
defaultValue?: number;
10+
error?: boolean;
1011
}
1112

1213
/**
@@ -17,17 +18,18 @@ interface Props {
1718
* @param {string} props.label - The label to be displayed next to the HeartRating component.
1819
* @param {function} props.onChange - Callback function to handle the change event when the rating is modified.
1920
* @param {number} [props.defaultValue] - The default value for the HeartRating component.
21+
* @param {boolean} props.error - If true, displays the label in an error state (typically red).
2022
* @returns {JSX.Element} The rendered ReviewRating component.
2123
*/
2224

23-
const ReviewRating = ({ name, label, onChange, defaultValue }: Props) => {
25+
const ReviewRating = ({ name, label, onChange, defaultValue, error }: Props) => {
2426
return (
2527
<Grid container justifyContent="flex-start" alignItems="center" item xs={12} md={6}>
2628
<Grid item>
2729
<HeartRating name={name} onChange={onChange} defaultValue={defaultValue || 0} />
2830
</Grid>
2931
<Grid item style={{ marginLeft: '25px' }}>
30-
<FormLabel>{label}</FormLabel>
32+
<FormLabel error={error}>{label}</FormLabel>
3133
</Grid>
3234
</Grid>
3335
);

0 commit comments

Comments
 (0)