Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 100 additions & 49 deletions frontend/apps/mobile/app/(app)/event/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ import {
useGetEventOccurrencesByEventId,
useGetOrganization,
useGetReviewAggregate,
useGetReviewByEventId,
} from "@skillspark/api-client";
import type { EventOccurrence, Organization } from "@skillspark/api-client";
import type { EventOccurrence, Organization, Review } from "@skillspark/api-client";
import { IconSymbol } from "@/components/ui/icon-symbol";
import { RATING_OPTIONS } from "@/constants/ratings";
import { AppColors, Shadows } from "@/constants/theme";
import { useOrgLinks } from "@/hooks/useOrgLinks";
import { BookmarkButton } from "@/components/BookmarkButton";
import { ReviewCard } from "@/components/ReviewCard";
import { useTranslation } from "react-i18next";
import { AboutPage } from "@/components/AboutPage";
import { ShareModal } from "@/components/ShareModal";
import { formatLocation } from "@/utils/format";
import { getRatingOption } from "@/utils/ratings";
import { EventImage } from "@/components/EventImage";
import { ExpandableText } from "@/components/ExpandableText";
import { ErrorScreen } from "@/components/ErrorScreen";
import LogoBgWrapper from "@/components/LogoBgWrapper";
import { useState } from "react";

function EventOccurrenceDetail({
Expand Down Expand Up @@ -66,6 +68,24 @@ function EventOccurrenceDetail({
const totalReviews = aggregate?.total_reviews ?? 0;
const ratingOption = getRatingOption(avgRating);

const { data: reviewsResp } = useGetReviewByEventId(
occurrence.event.id,
{ page: 1, page_size: 5, sort_by: "highest" },
{ query: { enabled: !!occurrence.event.id && totalReviews > 0 } },
);
const rawReviews =
reviewsResp?.status === 200 ? (reviewsResp.data as Review[]) : [];
const previewReview =
rawReviews.length > 0
? rawReviews.reduce<Review>(
(best, r) =>
Math.abs(r.rating - avgRating) < Math.abs(best.rating - avgRating)
? r
: best,
rawReviews[0],
)
: null;

return (
<SafeAreaView className="flex-1 bg-white" edges={["top", "bottom"]}>
{/* Header */}
Expand All @@ -89,7 +109,7 @@ function EventOccurrenceDetail({
style={{ color: AppColors.primaryText }}
numberOfLines={1}
>
{translate("org.class")}
{occurrence.event.title}
</Text>
<View className="w-8" />
</View>
Expand All @@ -107,36 +127,42 @@ function EventOccurrenceDetail({
</View>

{/* White content card overlapping image */}
<View className="bg-white rounded-t-[28px] -mt-7 px-[22px] pb-6">
<View className="bg-white -mt-7 px-[22px] pb-6">
{/* Drag handle */}
<View
className="w-[38px] h-1 rounded-sm self-center mt-3 mb-3.5"
style={{ backgroundColor: AppColors.borderLight }}
/>

{/* Title row with bookmark + share */}
<View className="mb-1">
<View className="absolute top-0 right-0 items-center z-10">
<View className="absolute top-0 right-0 flex-row items-center gap-2 z-10">
<TouchableOpacity
onPress={() => setShareVisible(true)}
activeOpacity={0.7}
className="h-9 w-9 items-center justify-center rounded-full border-2"
style={{ borderColor: AppColors.borderLight }}
>
<IconSymbol
name="square.and.arrow.up"
size={28}
color={AppColors.primaryText}
size={18}
color={AppColors.secondaryText}
/>
</TouchableOpacity>
<BookmarkButton eventId={occurrence.event.id} />
<BookmarkButton
eventId={occurrence.event.id}
iconSize={18}
className="h-9 w-9 items-center justify-center rounded-full border-2"
style={{ borderColor: AppColors.borderLight }}
/>
</View>
<Text
className="mr-10 text-[26px] font-nunito-bold leading-8"
className="mr-24 text-[26px] font-nunito-bold leading-8"
style={{ color: AppColors.primaryText }}
>
{occurrence.event.title}
</Text>
</View>

<LogoBgWrapper>
{/* Location */}
<View className="flex-row items-center gap-1 mb-2">
<IconSymbol name="location" size={22} color={AppColors.mutedText} />
Expand Down Expand Up @@ -165,34 +191,41 @@ function EventOccurrenceDetail({
</View>
)}

{!!orgId && (
{/* Bookings this week */}
<View className="flex-row items-center gap-1 mb-2">
<IconSymbol
name="flame.fill"
size={18}
color={AppColors.primaryBlue}
/>
<Text
className="text-[14px] font-nunito"
style={{ color: AppColors.primaryBlue }}
>
{occurrence.curr_enrolled}+ {translate("event.bookingsThisWeek")}
</Text>
</View>

{/* Org badge */}
{!!orgName && !!orgId && (
<TouchableOpacity
onPress={() => router.push(`../org/${orgId}`)}
className="flex-row items-center gap-1 mb-2"
activeOpacity={0.7}
className="self-start px-4 py-1.5 rounded-full mt-1"
style={{ backgroundColor: AppColors.savedBackground }}
>
<IconSymbol
name="person.fill"
size={22}
color={AppColors.mutedText}
/>
<Text
className="text-[14px] font-nunito underline"
style={{ color: AppColors.mutedText }}
className="text-[13px] font-nunito-bold"
style={{ color: AppColors.primaryBlue }}
>
{orgName}
</Text>
</TouchableOpacity>
)}

{/* Bookings this week */}
<Text
className="text-[14px] font-nunito"
style={{ color: AppColors.primaryBlue }}
>
{occurrence.curr_enrolled}+ {translate("event.bookingsThisWeek")}
</Text>
</LogoBgWrapper>
</View>


{/* About card */}
<View
className="mx-4 mb-4 rounded-2xl bg-white p-5"
Expand Down Expand Up @@ -226,41 +259,57 @@ function EventOccurrenceDetail({
style={Shadows.card}
>
<Text
className="mb-3 font-nunito-bold text-[18px]"
className="mb-4 font-nunito-bold text-[18px]"
style={{ color: AppColors.primaryText }}
>
{translate("event.reviews")}
</Text>

{/* Aggregate rating */}
{totalReviews > 0 ? (
<View className="flex-row items-center gap-2">
<Image
source={ratingOption.image}
style={{ width: 22, height: 22 }}
/>
<Text
className="text-[15px] font-nunito-bold"
style={{ color: AppColors.primaryText }}
>
{translate(ratingOption.labelKey!)}
</Text>
<Text
className="text-[13px] font-nunito"
style={{ color: AppColors.subtleText }}
>
({totalReviews})
</Text>
<View className="flex-row gap-3 items-start mb-2">
{/* Left: aggregate number + smiley + count */}
<View className="items-center">
<Text
className="font-nunito-bold"
style={{ color: AppColors.primaryText, fontSize: 40, lineHeight: 48 }}
>
{avgRating % 1 === 0
? avgRating.toFixed(0)
: avgRating.toFixed(1)}
</Text>
<Image
source={ratingOption.image}
style={{ width: 44, height: 44 }}
/>
<Text
className="text-[12px] font-nunito mt-1"
style={{ color: AppColors.subtleText }}
>
({totalReviews})
</Text>
</View>

{/* Right: preview review (no avatar — aggregate smiley already on left) */}
{previewReview && (
<View className="flex-1 -mr-5">
<ReviewCard review={previewReview} hideAvatar />
</View>
)}
</View>
) : (
<Text
className="text-[13px] font-nunito"
className="text-[13px] font-nunito mb-2"
style={{ color: AppColors.subtleText }}
>
{translate("review.noReviews")}
</Text>
)}
<View className="mt-4 items-center">

<View
style={{ height: 0.5, backgroundColor: AppColors.borderLight }}
className="mt-2 mb-3"
/>
<View className="items-center">
<Text
className="text-[13px] font-nunito underline"
style={{ color: AppColors.primaryText }}
Expand Down Expand Up @@ -292,6 +341,8 @@ function EventOccurrenceDetail({
</Text>
</TouchableOpacity>
</View>


</ScrollView>

<ShareModal
Expand Down
Loading
Loading