Gift Auction: Follow up (#6569)
This commit is contained in:
parent
048d531321
commit
d14da19bd8
@ -58,12 +58,15 @@
|
|||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
|
|
||||||
padding: 0.125rem 0.375rem;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 1rem;
|
||||||
|
padding: 0 0.375rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
font-size: 0.6875rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
line-height: 0.75rem;
|
|
||||||
|
|
||||||
backdrop-filter: blur(0.5rem);
|
backdrop-filter: blur(0.5rem);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.winningBadge {
|
.winningBadge {
|
||||||
|
margin-top: 0.125rem;
|
||||||
padding: 0.0625rem 0.5rem;
|
padding: 0.0625rem 0.5rem;
|
||||||
border-radius: 0.875rem;
|
border-radius: 0.875rem;
|
||||||
|
|
||||||
|
|||||||
@ -100,14 +100,6 @@ const GiftAuctionBidModal = ({
|
|||||||
|
|
||||||
const sliderMaxValue = Math.ceil(currentMinBid / BID_ROUNDING_STEP) * BID_ROUNDING_STEP + MAX_BID_AMOUNT_STEP;
|
const sliderMaxValue = Math.ceil(currentMinBid / BID_ROUNDING_STEP) * BID_ROUNDING_STEP + MAX_BID_AMOUNT_STEP;
|
||||||
|
|
||||||
const currentProgress = (currentMinBid - baseMinBid) / (sliderMaxValue - baseMinBid);
|
|
||||||
const adjustedMinBid = Math.floor(
|
|
||||||
(currentMinBid - MIN_SLIDER_PROGRESS * sliderMaxValue) / (1 - MIN_SLIDER_PROGRESS),
|
|
||||||
);
|
|
||||||
const giftMinBid = currentProgress > MIN_SLIDER_PROGRESS
|
|
||||||
? Math.max(1, adjustedMinBid)
|
|
||||||
: baseMinBid;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedBidAmount(currentMinBid);
|
setSelectedBidAmount(currentMinBid);
|
||||||
}, [currentMinBid]);
|
}, [currentMinBid]);
|
||||||
@ -137,9 +129,13 @@ const GiftAuctionBidModal = ({
|
|||||||
loadActiveGiftAuction({ giftId: renderingAuctionState.gift.id });
|
loadActiveGiftAuction({ giftId: renderingAuctionState.gift.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleRequestCustomValue = useLastCallback(() => {
|
||||||
|
openCustomBidModal();
|
||||||
|
});
|
||||||
|
|
||||||
const handleBadgeClick = useLastCallback(() => {
|
const handleBadgeClick = useLastCallback(() => {
|
||||||
if (isAtMaxValue) {
|
if (isAtMaxValue) {
|
||||||
openCustomBidModal();
|
handleRequestCustomValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -354,12 +350,14 @@ const GiftAuctionBidModal = ({
|
|||||||
<StarSlider
|
<StarSlider
|
||||||
className={styles.slider}
|
className={styles.slider}
|
||||||
defaultValue={currentMinBid}
|
defaultValue={currentMinBid}
|
||||||
minValue={giftMinBid}
|
minValue={baseMinBid}
|
||||||
minAllowedValue={currentMinBid}
|
minAllowedValue={currentMinBid}
|
||||||
|
minAllowedProgress={MIN_SLIDER_PROGRESS}
|
||||||
maxValue={sliderMaxValue}
|
maxValue={sliderMaxValue}
|
||||||
floatingBadgeDescription={sliderSecondaryText}
|
floatingBadgeDescription={sliderSecondaryText}
|
||||||
onChange={handleAmountChange}
|
onChange={handleAmountChange}
|
||||||
onBadgeClick={handleBadgeClick}
|
onBadgeClick={handleBadgeClick}
|
||||||
|
onCustomValueClick={handleRequestCustomValue}
|
||||||
shouldUseDynamicColor
|
shouldUseDynamicColor
|
||||||
shouldAllowCustomValue
|
shouldAllowCustomValue
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -23,12 +23,14 @@ type OwnProps = {
|
|||||||
defaultValue: number;
|
defaultValue: number;
|
||||||
minValue?: number;
|
minValue?: number;
|
||||||
minAllowedValue?: number;
|
minAllowedValue?: number;
|
||||||
|
minAllowedProgress?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
floatingBadgeDescription?: TeactNode;
|
floatingBadgeDescription?: TeactNode;
|
||||||
shouldUseDynamicColor?: boolean;
|
shouldUseDynamicColor?: boolean;
|
||||||
shouldAllowCustomValue?: boolean;
|
shouldAllowCustomValue?: boolean;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number) => void;
|
||||||
onBadgeClick?: NoneToVoidFunction;
|
onBadgeClick?: NoneToVoidFunction;
|
||||||
|
onCustomValueClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_POINTS = [50, 100, 500, 1000, 2000, 5000, 10000];
|
const DEFAULT_POINTS = [50, 100, 500, 1000, 2000, 5000, 10000];
|
||||||
@ -65,62 +67,63 @@ const SLIDER_COLORS = [
|
|||||||
'#40A920', // Green
|
'#40A920', // Green
|
||||||
'#E29A09', // Yellow
|
'#E29A09', // Yellow
|
||||||
'#ED771E', // Orange
|
'#ED771E', // Orange
|
||||||
'#E14542', // Red
|
'#E14741', // Red
|
||||||
'#596473', // Silver (100% only)
|
'#5B6676', // Silver
|
||||||
];
|
];
|
||||||
|
|
||||||
function getColorForProgress(progress: number): string {
|
function getColorForProgress(progress: number): string {
|
||||||
if (progress >= 1) return SLIDER_COLORS[SLIDER_COLORS.length - 1];
|
const index = Math.floor(progress * SLIDER_COLORS.length);
|
||||||
|
return SLIDER_COLORS[Math.min(index, SLIDER_COLORS.length - 1)];
|
||||||
const regularColorsCount = SLIDER_COLORS.length - 1;
|
|
||||||
const index = Math.floor(progress * regularColorsCount);
|
|
||||||
return SLIDER_COLORS[Math.min(index, regularColorsCount - 1)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const StarSlider = ({
|
const StarSlider = ({
|
||||||
maxValue,
|
maxValue,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
minValue,
|
minValue: minValueProp,
|
||||||
minAllowedValue,
|
minAllowedValue,
|
||||||
|
minAllowedProgress,
|
||||||
className,
|
className,
|
||||||
floatingBadgeDescription,
|
floatingBadgeDescription,
|
||||||
shouldUseDynamicColor,
|
shouldUseDynamicColor,
|
||||||
shouldAllowCustomValue,
|
shouldAllowCustomValue,
|
||||||
onChange,
|
onChange,
|
||||||
onBadgeClick,
|
onBadgeClick,
|
||||||
|
onCustomValueClick,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
const floatingBadgeContentRef = useRef<HTMLDivElement>();
|
const floatingBadgeContentRef = useRef<HTMLDivElement>();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const min = minValue ?? 1;
|
const baseMinValue = minValueProp ?? 1;
|
||||||
|
|
||||||
|
// Uses binary search - O(log n)
|
||||||
|
const actualMinValue = useMemo(() => {
|
||||||
|
if (!minAllowedProgress || !minAllowedValue || minAllowedValue <= baseMinValue) {
|
||||||
|
return baseMinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let low = baseMinValue;
|
||||||
|
let high = minAllowedValue;
|
||||||
|
|
||||||
|
while (low < high) {
|
||||||
|
const mid = Math.floor((low + high) / 2);
|
||||||
|
const testPoints = buildPoints(mid, maxValue);
|
||||||
|
const testProgress = getProgress(testPoints, minAllowedValue, mid);
|
||||||
|
const normalizedProgress = testProgress / testPoints.length;
|
||||||
|
|
||||||
|
if (normalizedProgress < minAllowedProgress) {
|
||||||
|
high = mid;
|
||||||
|
} else {
|
||||||
|
low = mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(baseMinValue, low);
|
||||||
|
}, [baseMinValue, minAllowedValue, minAllowedProgress, maxValue]);
|
||||||
|
|
||||||
const points = useMemo(() => {
|
const points = useMemo(() => {
|
||||||
const result = [];
|
return buildPoints(actualMinValue, maxValue);
|
||||||
|
}, [maxValue, actualMinValue]);
|
||||||
for (let i = 0; i < DEFAULT_POINTS.length; i++) {
|
|
||||||
if (DEFAULT_POINTS[i] <= min) continue;
|
|
||||||
|
|
||||||
if (DEFAULT_POINTS[i] < maxValue) {
|
|
||||||
result.push(DEFAULT_POINTS[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEFAULT_POINTS[i] >= maxValue) {
|
|
||||||
result.push(maxValue);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastPoint = DEFAULT_POINTS[DEFAULT_POINTS.length - 1];
|
|
||||||
let nextPoint = lastPoint + LARGE_STEP;
|
|
||||||
while (nextPoint < maxValue) {
|
|
||||||
result.push(nextPoint);
|
|
||||||
nextPoint += LARGE_STEP;
|
|
||||||
}
|
|
||||||
result.push(maxValue);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [maxValue, min]);
|
|
||||||
|
|
||||||
const [value, setValue] = useState(0);
|
const [value, setValue] = useState(0);
|
||||||
const [containerWidth, setContainerWidth] = useState(0);
|
const [containerWidth, setContainerWidth] = useState(0);
|
||||||
@ -129,14 +132,14 @@ const StarSlider = ({
|
|||||||
const startXRef = useRef<number | undefined>();
|
const startXRef = useRef<number | undefined>();
|
||||||
const prevBadgeWidth = usePrevious(badgeWidth);
|
const prevBadgeWidth = usePrevious(badgeWidth);
|
||||||
|
|
||||||
const badgeText = lang.number(getValue(points, value, min));
|
const badgeText = lang.number(getValue(points, value, actualMinValue));
|
||||||
|
|
||||||
const minAllowedProgress = minAllowedValue !== undefined
|
const minSliderProgress = minAllowedValue !== undefined
|
||||||
? getProgress(points, minAllowedValue, min) : 0;
|
? getProgress(points, minAllowedValue, actualMinValue) : 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(getProgress(points, defaultValue, min));
|
setValue(getProgress(points, defaultValue, actualMinValue));
|
||||||
}, [defaultValue, points, min]);
|
}, [defaultValue, points, actualMinValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!floatingBadgeContentRef.current) return;
|
if (!floatingBadgeContentRef.current) return;
|
||||||
@ -183,10 +186,10 @@ const StarSlider = ({
|
|||||||
|
|
||||||
const handleChange = useLastCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = useLastCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const rawValue = Number(event.currentTarget.value);
|
const rawValue = Number(event.currentTarget.value);
|
||||||
const clampedValue = Math.max(rawValue, minAllowedProgress);
|
const clampedValue = Math.max(rawValue, minSliderProgress);
|
||||||
setValue(clampedValue);
|
setValue(clampedValue);
|
||||||
|
|
||||||
const resultValue = getValue(points, clampedValue, min);
|
const resultValue = getValue(points, clampedValue, actualMinValue);
|
||||||
onChange(resultValue);
|
onChange(resultValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,8 +206,20 @@ const StarSlider = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlePointerUp = useLastCallback(() => {
|
const handlePointerUp = useLastCallback((e: React.PointerEvent<HTMLInputElement>) => {
|
||||||
startXRef.current = undefined;
|
startXRef.current = undefined;
|
||||||
|
|
||||||
|
if (!isDragging && onCustomValueClick && containerRef.current) {
|
||||||
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
|
const clickZoneWidth = 1.875 * REM;
|
||||||
|
|
||||||
|
const isInIconArea = e.clientX >= rect.right - clickZoneWidth && e.clientX <= rect.right;
|
||||||
|
|
||||||
|
if (isInIconArea) {
|
||||||
|
onCustomValueClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -291,7 +306,7 @@ const StarSlider = ({
|
|||||||
type="range"
|
type="range"
|
||||||
min={0}
|
min={0}
|
||||||
max={points.length}
|
max={points.length}
|
||||||
defaultValue={getProgress(points, defaultValue, min)}
|
defaultValue={getProgress(points, defaultValue, actualMinValue)}
|
||||||
step="any"
|
step="any"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
@ -305,6 +320,35 @@ const StarSlider = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function buildPoints(minValue: number, maxValue: number): number[] {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < DEFAULT_POINTS.length; i++) {
|
||||||
|
if (DEFAULT_POINTS[i] <= minValue) continue;
|
||||||
|
|
||||||
|
if (DEFAULT_POINTS[i] < maxValue) {
|
||||||
|
result.push(DEFAULT_POINTS[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEFAULT_POINTS[i] >= maxValue) {
|
||||||
|
result.push(maxValue);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastPoint = DEFAULT_POINTS[DEFAULT_POINTS.length - 1];
|
||||||
|
const stepsNeeded = Math.ceil((minValue - lastPoint) / LARGE_STEP);
|
||||||
|
let nextPoint = lastPoint + Math.max(1, stepsNeeded) * LARGE_STEP;
|
||||||
|
|
||||||
|
while (nextPoint < maxValue) {
|
||||||
|
result.push(nextPoint);
|
||||||
|
nextPoint += LARGE_STEP;
|
||||||
|
}
|
||||||
|
result.push(maxValue);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function getProgress(points: number[], value: number, minValue: number) {
|
function getProgress(points: number[], value: number, minValue: number) {
|
||||||
const pointIndex = points.findIndex((point) => value <= point);
|
const pointIndex = points.findIndex((point) => value <= point);
|
||||||
const prevPoint = points[pointIndex - 1] || minValue;
|
const prevPoint = points[pointIndex - 1] || minValue;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user