Skip to content

Commit

Permalink
Merge pull request #65 from jakkemomo/feature/add_profile_events_lists
Browse files Browse the repository at this point in the history
feat: add EventSliders on ProfilePage
  • Loading branch information
algoritmi4 authored Jun 17, 2024
2 parents 58aba6b + f523715 commit f743e5f
Show file tree
Hide file tree
Showing 25 changed files with 472 additions and 191 deletions.
6 changes: 6 additions & 0 deletions public/images/svg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 25 additions & 1 deletion src/entities/event/api/eventApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ export const eventApi = baseApi.injectEndpoints({
}),
invalidatesTags: ['EVENTS_TAG']
}),
getUserCreatedEvents: build.query<IApiResponse<IEvent[]>, number>({
query: (user_id) => ({
url: `users/${user_id}/events/created/`,
method: 'GET'
}),
providesTags: ['EVENTS_TAG']
}),
getUserPlannedEvents: build.query<IApiResponse<IEvent[]>, number>({
query: (user_id) => ({
url: `users/${user_id}/events/planned/`,
method: 'GET'
}),
providesTags: ['EVENTS_TAG']
}),
getUserFinishedEvents: build.query<IApiResponse<IEvent[]>, number>({
query: (user_id) => ({
url: `users/${user_id}/events/finished/`,
method: 'GET'
}),
providesTags: ['EVENTS_TAG']
}),
})
})

Expand All @@ -72,5 +93,8 @@ export const {
useRegisterToEventMutation,
useLeaveFromEventMutation,
useLikeEventMutation,
useUnlikeEventMutation
useUnlikeEventMutation,
useGetUserCreatedEventsQuery,
useGetUserPlannedEventsQuery,
useGetUserFinishedEventsQuery
} = eventApi;
127 changes: 110 additions & 17 deletions src/entities/event/ui/EventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {IEvent} from "../model/types";
import { Link } from "react-router-dom";
import Svg from "@/shared/ui/Svg";
import { useLikeEventMutation, useUnlikeEventMutation } from "../api/eventApi";
import cx from 'classnames';

export interface IEventCard {
event: IEvent;
size?: 'sm' | 'lg';
}

export function EventCard({ event }: IEventCard): ReactElement {
export function EventCard({ event, size = 'lg' }: IEventCard): ReactElement {
const [isFavorite, setIsFavorite] = useState(event.is_favorite);

const [likeEvent] = useLikeEventMutation();
Expand Down Expand Up @@ -37,39 +39,130 @@ export function EventCard({ event }: IEventCard): ReactElement {
}, [event])

return (
<div className="w-full flex flex-col max-w-[270px] mr-[45px]">
<div className={
cx(
"w-full flex flex-col", {
'max-w-[225px] mr-[40px]': size === 'sm',
'max-w-[270px] mr-[45px]': size === 'lg'
}
)}>
<div className="flex justify-between">
<p className="text-[14px] font-medium capitalize">{event.category?.name}</p>
<p className={
cx(
"capitalize", {
'text-[12px]': size === 'sm',
'text-[14px]': size === 'lg'
}
)}>{event.category?.name}</p>
<Svg
id="heart-icon"
className="w-6 h-6 cursor-pointer"
className={
cx(
"cursor-pointer", {
'w-5 h-5': size === 'sm',
'w-6 h-6': size === 'lg'
}
)}
viewBox="0 0 24 24"
onClick={isFavorite ? onUnlike : onLike}
extraUseClass={isFavorite ? "!fill-but-primary stroke-but-primary" : "stroke-text-black"}
/>
</div>
<Link to={`/events/${event.id}`}>
<figure className="group flex flex-col cursor-pointer rounded-12 max-h-[188px] mt-[7px] overflow-hidden">
<img className="group-hover:scale-105 duration-300 ease-in-out rounded-t-def h-[143px] object-cover" src={`https://storage.googleapis.com/meetups-dev/media/${event.image_url}`} alt={`Изображение ивента ${event.name}`} />
<div className={`h-[45px] bg-gray rounded-b-def flex items-center justify-center pl-[16px] pr-[7px] relative ${event.name.length > 21 && "before:w-[60px] before:rounded-b-[12px] before:absolute before:right-0 before:h-full before:bg-text-fade-out"}`}>
<figcaption className="group-hover:font-bold capitalize text-[20px] font-semibold text-text-black overflow-hidden whitespace-nowrap text-clip">{event.name}</figcaption>
<figure className={
cx(
"group flex flex-col cursor-pointer rounded-12 max-h-[188px] mt-[7px] overflow-hidden", {
'max-h-[157px]': size === 'sm',
'max-h-[188px]': size === 'lg'
}
)}>
<img className={
cx(
"group-hover:scale-105 duration-300 ease-in-out rounded-t-def object-cover", {
'h-[120px]': size === 'sm',
'h-[143px]': size === 'lg'
}
)
} src={`https://storage.googleapis.com/meetups-dev/media/${event.image_url}`} alt={`Изображение ивента ${event.name}`} />
<div className={
cx(
`h-[45px] bg-gray rounded-b-def flex items-center justify-center pl-[16px] pr-[7px] relative ${event.name.length > 21 && "before:w-[60px] before:rounded-b-[12px] before:absolute before:right-0 before:h-full before:bg-text-fade-out"}`, {
'h-[37px]': size === 'sm',
'h-[45px]': size === 'lg'
}
)}>
<figcaption className={
cx(
"group-hover:font-bold capitalize font-semibold text-text-black overflow-hidden whitespace-nowrap text-clip", {
'text-[16px]': size === 'sm',
'text-[20px]': size === 'lg'
}
)
}>{event.name}</figcaption>
</div>
</figure>
</Link>
<div className="flex justify-between mt-2.5">
<div className="flex flex-col leading-def">
<p className="text-[18px] font-medium">{new Date(event.start_date).toLocaleDateString('ru-RU', {day: 'numeric', month: 'long'})}</p>
<p className="text-[18px] font-medium mt-0.5">{event.start_time ? event.start_time.slice(0, 5) : ''}</p>
<div className={
cx(
"flex justify-between", {
'mt-2': size === 'sm',
'mt-2.5': size === 'lg'
}
)
}>
<div className={
cx(
"flex flex-col font-medium", {
'text-[14px] leading-[18px]': size === 'sm',
'text-[18px] leading-def': size === 'lg'
}
)
}>
<p>{new Date(event.start_date).toLocaleDateString('ru-RU', {day: 'numeric', month: 'long'})}</p>
<p className="mt-0.5">{event.start_time ? event.start_time.slice(0, 5) : ''}</p>
</div>
<div className="flex flex-col">
<div className={
cx(
"flex flex-col", {
'text-[12px] leading-[15px]': size === 'sm',
'text-[14px] leading-[18px]': size === 'lg'
}
)}>
<div className="flex items-center">
<p className="text-[14px]">6/12</p>
<Svg viewBox="0 0 34 34" id="person-quantity-icon" className="w-6 h-6 ml-1" />
<p>6/12</p>
<Svg
viewBox={
cx("", {
'0 0 30 30': size === 'sm',
'0 0 32 32': size === 'lg'
})
}
id="person-quantity-icon"
className={
cx(
"ml-1", {
'w-5 h-5': size === 'sm',
'w-6 h-6': size === 'lg'
}
)
}
/>
</div>
{
(!!event.average_rating) && (
<div className="flex items-start mt-0.5 self-end">
<p className="text-[14px] font-medium">{event.average_rating}</p>
<Svg id='rating-star' extraUseClass="!fill-current" className='w-[18px] h-[18px] ml-[7px]' />
<p>{event.average_rating}</p>
<Svg
id='rating-star'
extraUseClass="!fill-current"
className={
cx(
'ml-[7px]', {
'w-[15px] h-[15px]': size === 'sm',
'w-[18px] h-[18px]': size === 'lg'
}
)
} />
</div>
)
}
Expand Down
46 changes: 17 additions & 29 deletions src/features/eventChoice/ui/EventSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
import { SlickSlider } from "@/shared";
import { ISlickSliderProps } from "@/shared/ui/SlickSlider/SlickSlider";
import { ReactElement, useEffect, useState } from "react";
import { IEvent } from "@/entities/event/model/types";
import { EventCard } from "@/entities/event";

interface IEventSlider {
events: IEvent[];
export interface IEventSlider extends Omit<ISlickSliderProps, 'extraSettings'> {
slidesLength: number;
}

export function EventSlider({ events }: IEventSlider): ReactElement {
const cards = events.map((el) => <EventCard key={el.id} event={el} />);

const [width, setWidth] = useState(103.7);
const [slidesToShow, setSlidesToShow] = useState(4);
export function EventSlider({ children, slidesLength, arrowsExtraClasses }: IEventSlider): ReactElement {
const [slidesToShow, setSlidesToShow] = useState(slidesLength);
const [sliderWidth, setSliderWidth] = useState(100);

useEffect(() => {
switch (events.length) {
case 1:
setWidth(26);
setSlidesToShow(1);
break
case 2:
setWidth(52);
setSlidesToShow(2);
break
case 3:
setWidth(78);
setSlidesToShow(3);
break
default:
setWidth(103.7);
setSlidesToShow(4);
if (children.length < slidesLength) {
setSlidesToShow(children.length);
setSliderWidth(100 - ((slidesLength - children.length) * (100 / slidesLength)));
} else {
setSlidesToShow(slidesLength);
setSliderWidth(100);
}
}, [events.length]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children.length]);

const settings = {
slidesToShow,
slidesToScroll: 2,
speed: 400,
className: `mt-3 w-[${width}%] min-h-[230px]`
className: `mt-5 max-w-[${String(sliderWidth).slice(0, 4)}%] min-h-[230px]`
}

return (
<SlickSlider extraSettings={settings} arrowsExtraClasses={{rightArrow: 'right-[-12px] top-[110px]', leftArrow: 'left-[-42px] top-[110px]'}}>
{cards}
<SlickSlider extraSettings={settings} arrowsExtraClasses={arrowsExtraClasses}>
{children}
</SlickSlider>
)
}
28 changes: 12 additions & 16 deletions src/features/review/ui/ReviewSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,31 @@ import { ReviewCard } from "@/entities/review/ui/ReviewCard";

interface IReviewSlider {
reviews: IReview[];
slidesLength: number;
}

export function ReviewSlider({ reviews }: IReviewSlider): ReactElement {
export function ReviewSlider({ reviews, slidesLength }: IReviewSlider): ReactElement {
const cards = reviews.map((el, index) => <ReviewCard key={index} review={el} />);

const [width, setWidth] = useState(100);
const [slidesToShow, setSlidesToShow] = useState(3);
const [slidesToShow, setSlidesToShow] = useState(slidesLength);
const [sliderWidth, setSlidesWidth] = useState(100);

useEffect(() => {
switch (reviews.length) {
case 1:
setWidth(33);
setSlidesToShow(1);
break
case 2:
setWidth(66);
setSlidesToShow(2);
break
default:
setWidth(103.7);
setSlidesToShow(3);
if (reviews.length < slidesLength) {
setSlidesToShow(reviews.length);
setSlidesWidth(100 - ((slidesLength - reviews.length) * (100 / slidesLength)));
} else {
setSlidesToShow(slidesLength);
setSlidesWidth(100);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reviews.length]);

const settings = {
slidesToShow,
slidesToScroll: 1,
speed: 400,
className: `mt-3 w-[${width}%] min-h-[285px]`
className: `mt-5 max-w-[${String(sliderWidth).slice(0, 4)}%] min-h-[230px]`
}

return (
Expand Down
19 changes: 16 additions & 3 deletions src/pages/event/EventPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {EventPageContext} from './model/EventPageContext';
import {mockReviews} from './model/consts';
import {useGetReviewsQuery} from '@/entities/review/api/reviewApi';
import { ParticipantsPopup } from '@/entities/eventParticipants';
import { SliderEmptyElem } from '@/shared';
import { useLogServerError } from '@/shared/lib/hooks';
import { getEventsCards } from '@/widgets/EventsList/model/getEventsCards';

export function EventPage(): ReactElement {
const [isPageReady, setIsPageReady] = useState(false);
Expand Down Expand Up @@ -52,8 +55,8 @@ export function EventPage(): ReactElement {
isError: isReviewsError
} = useGetReviewsQuery(Number(eventId));

isTopEventsError && console.log(`Ошибка при получении ивентов - ${JSON.stringify(topEventsError)}`);
isReviewsError && console.log(`Ошибка при получении отзывов - ${JSON.stringify(reviewsError)}`);
useLogServerError(isTopEventsError, 'ивентов', topEventsError);
useLogServerError(isReviewsError, 'отзывов', reviewsError);

const isOwner = event?.created_by.id === profile?.id;
const { isFavorite } = useAppSelector((state) => state.eventInfo);
Expand All @@ -73,6 +76,9 @@ export function EventPage(): ReactElement {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [eventId]);

const filteredEvents = topEvents.results.filter((el) => el.id !== event?.id);
const topEventsList = getEventsCards(filteredEvents, 'lg');

if (isEventLoading || isReviwesLoading || !isPageReady) {
return <EventLoader />;
}
Expand Down Expand Up @@ -108,7 +114,14 @@ export function EventPage(): ReactElement {
<CreatorDetails creator={event.created_by}/>
<Location event={event}/>
<ReviewsRow reviews={mockReviews} rating={event.average_rating}/>
<EventsList listTitle="Рекомендации для Вас" isLoading={isTopEventsLoading} data={topEvents.results.filter((el) => el.id !== event.id)} extraClasses="mt-[50px]" />
<EventsList
listTitle="Рекомендации для Вас"
isLoading={isTopEventsLoading}
extraClasses="mt-[50px]"
slidesLength={4}
arrowsExtraClasses={{rightArrow: 'right-[-12px] top-[110px]', leftArrow: 'left-[-42px] top-[110px]'}}
emptyElement={<SliderEmptyElem text="Не найдено" />}
>{topEventsList}</EventsList>
</main>
</EventPageContext.Provider>
)
Expand Down
Loading

0 comments on commit f743e5f

Please sign in to comment.