Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | 6x 6x 98x 98x 95x 95x 196x | import { Box, Stack } from "@mui/material";
import { WarningIcon } from "@phosphor-icons/react";
import GiveAlert from "@shared/GiveAlert/GiveAlert";
import GiveStarRating from "@shared/StarRating/GiveStarRating";
import GiveSwitch from "@shared/Switch/GiveSwitch";
import GiveText from "@shared/Text/GiveText";
import { styled } from "@theme/v2/Provider";
import { formatAverage, reviewCountLabel } from "./EventReviewSummary";
import ReviewRatingDistribution from "./ReviewRatingDistribution";
import { EventReviewStats, ReviewRatingFilter } from "./review.types";
type Props = {
stats: EventReviewStats;
/**
* The summary read failed. It is a separate request from the list, so this
* column can be the only thing on the tab with nothing to show.
*/
isStatsError?: boolean;
ratings: ReviewRatingFilter[];
awaitingReplyOnly: boolean;
onToggleRating: (rating: ReviewRatingFilter) => void;
onToggleAwaitingReply: () => void;
};
export const STATS_UNAVAILABLE =
"The ratings summary couldn't be loaded. The reviews below are unaffected.";
/**
* The Reviews tab's right-hand column (frames 7501-72341 / 7508-77120):
* the event's overall score, the awaiting-reply toggle, and a checkbox per star
* whose bar doubles as the distribution.
*
* The distribution is read from the summary endpoint, so it keeps describing
* the whole event while the checkboxes narrow the list beside it.
*
* That endpoint failing is not the same as the event scoring zero. The score,
* the stars and the bars all come out of it, and the hook's fallback stats are
* zeros — so on a failure they are not drawn at all: a `0.00` and five `0 %`
* bars sitting beside a full list would be the tab inventing the event's rating.
* The filters stay, because they act on the list read, which is fine.
*/
const EventReviewSidebar = ({
stats,
isStatsError,
ratings,
awaitingReplyOnly,
onToggleRating,
onToggleAwaitingReply,
}: Props) => {
const counts: Record<number, number> = {
1: stats.numRating1,
2: stats.numRating2,
3: stats.numRating3,
4: stats.numRating4,
5: stats.numRating5,
};
return (
<Stack gap="32px" data-testid="event-review-sidebar">
{isStatsError ? (
<GiveAlert
type="warning"
variant="notice"
Icon={<WarningIcon size={20} />}
description={STATS_UNAVAILABLE}
dataTestId="event-review-stats-error"
/>
) : (
<Summary>
<Stack direction="row" gap="8px" alignItems="baseline">
<GiveText variant="h5" color="primary">
{formatAverage(stats.averageRating)}
</GiveText>
<GiveText variant="bodyS" color="secondary">
{reviewCountLabel(stats.total)}
</GiveText>
</Stack>
<Stars>
<GiveStarRating value={stats.averageRating} size={24} />
</Stars>
</Summary>
)}
<Stack gap="16px">
{/* "Turn on the toggle to show only comments that haven't been replied
to yet" — annotation on frame 7506-74967. */}
<FilterCard>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
gap="12px"
>
<GiveText variant="bodyS" color="primary">
Awaiting reply from me
</GiveText>
<GiveSwitch
checked={awaitingReplyOnly}
onChange={onToggleAwaitingReply}
inputProps={{ "aria-label": "Awaiting reply from me" }}
data-testid="event-review-awaiting-toggle"
/>
</Stack>
</FilterCard>
{/* "Clicking checkbox will filter the comments by the star" —
annotation on frame 7506-74961. The rows themselves are shared with
the live event page — see ReviewRatingDistribution. */}
<FilterCard>
<ReviewRatingDistribution
counts={counts}
total={stats.total}
selected={ratings}
onToggle={onToggleRating}
countsUnavailable={isStatsError}
testIdPrefix="event-review-filter"
/>
</FilterCard>
</Stack>
</Stack>
);
};
export default EventReviewSidebar;
/**
* Stacked while the sidebar is a narrow column (frame 7501-73473: the score on
* one line, the stars below it). Once it spans the page the mockup puts all
* three on one row with the stars leading (frame 7506-74235) — which is also
* where the vertical stack starts to look like wasted height.
*
* The public event page keeps the stacked form at every width (frame
* 7508-78240), so this belongs here rather than in a shared summary.
*/
const Summary = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: "12px",
[theme.breakpoints.down("v2_md")]: {
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap",
},
}));
const Stars = styled(Box)(({ theme }) => ({
lineHeight: 0,
[theme.breakpoints.down("v2_md")]: { order: -1 },
}));
const FilterCard = styled(Box)(({ theme }) => ({
padding: "16px",
borderRadius: `${theme?.customs?.radius?.medium}px`,
border: `1px solid ${theme.palette.border?.primary}`,
backgroundColor: theme.palette.background.paper,
}));
|