Skip to content

Commit 5ba90c0

Browse files
authored
Merge pull request #109 from Yeoun-project/fix/minor-corrections
[Fix] 노션 수정사항 & UT Review Form 추가
2 parents 42fd9bf + 7bc0b8b commit 5ba90c0

21 files changed

Lines changed: 619 additions & 345 deletions

frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<title>여운</title>
1212
</head>
1313
<body>
14+
<div id="pop-up"></div>
1415
<div id="modal"></div>
1516
<div id="toast"></div>
1617
<div id="root"></div>

frontend/src/components/backgroundBanner/BackgroundBanner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Logo from '/logo.svg';
44
const BackgroundBanner = () => {
55
return (
66
<div className="fixed top-0 left-0 hidden h-full w-[100vw] overflow-y-scroll bg-[url(/gradient-background.svg)] bg-cover bg-center bg-no-repeat xl:block">
7-
<div className="absolute left-[5%] z-50 p-20">
7+
<div className="absolute top-0 bottom-0 left-[5%] z-50 flex h-svh flex-col items-start justify-center p-20">
88
<img src={Logo} alt="여운" className="mb-4" />
99
<p className="mb-2 text-[32px]">
1010
안녕하세요 :) <br />

frontend/src/components/backgroundBanner/Rating.tsx

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,150 @@
1-
import { useState } from 'react';
2-
3-
import EmptyRating from '../../assets/Icons/rating/emptyRating.svg?react';
4-
import HalfRating from '../../assets/Icons/rating/halfRating.svg?react';
5-
import FillRating from '../../assets/Icons/rating/fillRating.svg?react';
1+
import React, { useState, useRef, useCallback } from 'react';
2+
import EmptyRatingIcon from '../../assets/Icons/rating/emptyRating.svg?react';
3+
import HalfRatingIcon from '../../assets/Icons/rating/halfRating.svg?react';
4+
import FillRatingIcon from '../../assets/Icons/rating/fillRating.svg?react';
5+
import { useIsMobile } from '../../hooks/useIsMobile';
66

77
interface RatingProps {
88
maxRatingValue?: number;
99
currentRating: number;
1010
setCurrentRating: (rating: number) => void;
11+
starSize?: number;
12+
readOnly?: boolean;
13+
handleSubmit?: () => void;
1114
}
1215

13-
const getStar = (index: number, ratingValue: number) => {
14-
const fullStars = Math.floor(ratingValue);
15-
const hasHalfStar = ratingValue % 1 !== 0 && index === fullStars;
16-
17-
if (index < fullStars) {
18-
return <FillRating width={48} height={48} />;
19-
}
20-
if (hasHalfStar) {
21-
return <HalfRating width={48} height={48} />;
16+
const StarDisplay = React.memo(
17+
({ type, size }: { type: 'fill' | 'half' | 'empty'; size: number }) => {
18+
if (type === 'fill') return <FillRatingIcon width={size} height={size} />;
19+
if (type === 'half') return <HalfRatingIcon width={size} height={size} />;
20+
return <EmptyRatingIcon width={size} height={size} />;
2221
}
23-
return <EmptyRating width={48} height={48} />;
24-
};
22+
);
2523

26-
const Rating = ({ maxRatingValue = 5, setCurrentRating, currentRating }: RatingProps) => {
24+
const Rating = ({
25+
maxRatingValue = 5,
26+
currentRating,
27+
setCurrentRating,
28+
starSize = 48,
29+
readOnly = false,
30+
handleSubmit = () => {},
31+
}: RatingProps) => {
2732
const [hoverRating, setHoverRating] = useState<number | null>(null);
33+
const [isDragging, setIsDragging] = useState(false);
34+
const ratingContainerRef = useRef<HTMLDivElement>(null);
35+
const isMobile = useIsMobile();
36+
37+
const getRatingToShow = readOnly
38+
? currentRating
39+
: hoverRating !== null
40+
? hoverRating
41+
: currentRating;
42+
43+
const calculateRatingFromX = useCallback(
44+
(clientX: number): number => {
45+
if (!ratingContainerRef.current) return 0;
46+
47+
const rect = ratingContainerRef.current.getBoundingClientRect();
48+
49+
const x = clientX - rect.left;
50+
51+
let newRating = (x / rect.width) * maxRatingValue;
52+
53+
newRating = Math.round(newRating * 2) / 2;
54+
55+
newRating = Math.max(0, Math.min(newRating, maxRatingValue));
56+
57+
return newRating;
58+
},
59+
[maxRatingValue]
60+
);
61+
62+
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
63+
if (readOnly) return;
64+
65+
if (isMobile) {
66+
setIsDragging(true);
67+
68+
event.currentTarget.setPointerCapture(event.pointerId);
69+
70+
const newRating = calculateRatingFromX(event.clientX);
71+
setHoverRating(newRating);
72+
setCurrentRating(newRating);
73+
}
74+
};
2875

29-
const handleMouseMove = (index: number, event: React.MouseEvent<HTMLSpanElement>) => {
30-
const starElement = event.currentTarget;
31-
const rect = starElement.getBoundingClientRect();
32-
const mouseX = event.clientX - rect.left;
33-
const starWidth = rect.width;
76+
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
77+
if (readOnly) return;
3478

35-
if (mouseX <= starWidth / 2) {
36-
setHoverRating(index + 0.5);
79+
if (isMobile) {
80+
if (isDragging) {
81+
const newRating = calculateRatingFromX(event.clientX);
82+
setHoverRating(newRating);
83+
setCurrentRating(newRating);
84+
}
3785
} else {
38-
setHoverRating(index + 1);
86+
const newRating = calculateRatingFromX(event.clientX);
87+
setHoverRating(newRating);
3988
}
4089
};
4190

42-
const handleMouseLeave = () => {
43-
setHoverRating(null);
91+
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
92+
if (readOnly) return;
93+
94+
if (isMobile && isDragging) {
95+
event.currentTarget.releasePointerCapture(event.pointerId);
96+
setIsDragging(false);
97+
}
98+
99+
if (isMobile) {
100+
handleSubmit();
101+
}
44102
};
45103

46-
const handleClick = (index: number) => {
47-
const ratingToSet = hoverRating !== null ? hoverRating : index + 0.5;
104+
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
105+
if (readOnly || isMobile) return;
106+
107+
const ratingToSet = hoverRating !== null ? hoverRating : calculateRatingFromX(event.clientX);
48108
setCurrentRating(ratingToSet);
49109
};
50110

111+
const handleMouseLeave = () => {
112+
if (readOnly || isMobile || isDragging) return;
113+
setHoverRating(null);
114+
};
115+
116+
const starsToRender = [];
117+
for (let i = 0; i < maxRatingValue; i++) {
118+
const starPoint = i + 1;
119+
let starType: 'fill' | 'half' | 'empty' = 'empty';
120+
121+
if (getRatingToShow >= starPoint) {
122+
starType = 'fill';
123+
} else if (getRatingToShow >= starPoint - 0.5) {
124+
starType = 'half';
125+
}
126+
starsToRender.push(<StarDisplay key={i} type={starType} size={starSize} />);
127+
}
128+
51129
return (
52-
<div className="inline-flex items-center" onMouseLeave={handleMouseLeave}>
53-
{[...Array(maxRatingValue)].map((_, index) => {
54-
const ratingValue = hoverRating !== null ? hoverRating : currentRating;
55-
return (
56-
<span
57-
className="cursor-pointer"
58-
key={index}
59-
onMouseMove={(e) => handleMouseMove(index, e)}
60-
onClick={() => handleClick(index)}
61-
>
62-
{getStar(index, ratingValue)}
63-
</span>
64-
);
65-
})}
130+
<div
131+
ref={ratingContainerRef}
132+
className={`inline-flex max-w-[240px] cursor-pointer items-center`}
133+
style={{ touchAction: isMobile && !readOnly ? 'none' : 'auto' }}
134+
onPointerDown={handlePointerDown}
135+
onPointerMove={handlePointerMove}
136+
onPointerUp={handlePointerUp}
137+
onClick={handleClick}
138+
onMouseLeave={handleMouseLeave}
139+
role="slider"
140+
aria-valuenow={currentRating}
141+
aria-valuemin={0}
142+
aria-valuemax={maxRatingValue}
143+
aria-readonly={readOnly}
144+
aria-label={`Rating: ${currentRating} out of ${maxRatingValue} stars`}
145+
tabIndex={readOnly ? -1 : 0}
146+
>
147+
{starsToRender}
66148
</div>
67149
);
68150
};

frontend/src/components/backgroundBanner/ReviewForm.tsx

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
import { useState } from 'react';
22
import Rating from './Rating';
3+
import writeUtReview from '../../services/api/writeUtReview';
34

45
const ReviewForm = () => {
6+
const completeReview = localStorage.getItem('utReview') === 'complete';
7+
58
const [currentRating, setCurrentRating] = useState<number>(0);
69
const [review, setReview] = useState<string>('');
7-
const [submitComplete, setSubmitComplete] = useState(false);
10+
const [submitComplete, setSubmitComplete] = useState(completeReview);
811

9-
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
12+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
1013
e.preventDefault();
11-
setSubmitComplete(true);
12-
setCurrentRating(0);
13-
setReview('소중한 의견 남겨주셔서 감사합니다!');
14+
try {
15+
await writeUtReview({ starRate: currentRating, message: review });
16+
setSubmitComplete(true);
17+
setCurrentRating(0);
18+
setReview('소중한 의견을 남겨주셔서 감사합니다!');
19+
localStorage.setItem('utReview', 'complete');
20+
} catch (error) {
21+
console.log(error);
22+
}
1423
};
1524

1625
return (
17-
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
26+
<form className="flex w-full flex-col gap-4" onSubmit={handleSubmit}>
1827
<Rating
1928
currentRating={currentRating}
2029
setCurrentRating={(rating) => {
@@ -28,8 +37,12 @@ const ReviewForm = () => {
2837
disabled={submitComplete}
2938
name="review"
3039
id="review"
31-
className="font-desc mb-2 h-[290px] w-full resize-none rounded-2xl border border-[#717171]/30 bg-white px-8 py-6 transition-colors outline-none disabled:text-[#EC69B8]"
32-
placeholder="서비스를 사용하면서 불편했던 점이나 개선이 필요한 부분을 작성해주세요"
40+
className="font-desc mb-2 h-[180px] w-full resize-none rounded-2xl border border-[#717171]/30 bg-white px-8 py-6 transition-colors outline-none disabled:text-[#EC69B8]"
41+
placeholder={
42+
submitComplete
43+
? '소중한 의견 남겨주셔서 감사합니다!'
44+
: '서비스를 사용하면서 불편했던 점이나 개선이 필요한 부분을 작성해주세요'
45+
}
3346
value={review}
3447
onChange={(e) => setReview(e.target.value)}
3548
/>

frontend/src/components/onboarding/OnBoardingStepOne.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const OnBoardingStepOne = () => {
1010
<div className="my-12 flex justify-center overflow-hidden p-2">
1111
<Circle size={260} animate>
1212
<p className="text-blur p-4 text-lg break-keep text-black">
13-
다시 태어난다면, 당신은 어떻게 살고싶나요?
13+
다시 태어난다면, 당신은 어떻게 살고 싶나요?
1414
</p>
1515
</Circle>
1616
</div>

0 commit comments

Comments
 (0)