From b5a60c0a8f61dd87f6f05457847eed71b16fad1c Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 24 May 2024 22:05:05 +0300 Subject: [PATCH 1/4] fix: add RouteGuard, change route-access logic and some general fixes --- src/app/appRouter.tsx | 52 ++++++++--------- src/app/guards/AuthGuard.tsx | 15 ----- src/app/guards/RouteGuard.tsx | 33 +++++++++++ src/app/rootReducer.ts | 2 +- src/entities/event/lib/useEventActions.ts | 57 +++++++++++-------- src/entities/event/model/types.ts | 1 - src/entities/event/ui/EventCard.tsx | 13 ++++- .../eventParticipants/ui/ParticipantCard.tsx | 33 ++++++++--- .../ui/ParticipantsPopup.tsx | 10 ++-- src/entities/profile/api/profileApi.ts | 1 + src/entities/session/api/sessionApi.ts | 6 +- src/entities/session/model/slice.ts | 8 +-- src/features/authentication/logout/index.ts | 1 - .../authentication/logout/ui/LogoutButton.tsx | 40 ------------- src/features/burgerMenu/model/types.ts | 5 -- src/features/burgerMenu/ui/burgerMenu.tsx | 24 ++++++-- src/features/burgerMenu/ui/menuItem.tsx | 32 +++++------ src/features/eventPage/ui/HeaderDetails.tsx | 33 ++++++++++- src/pages/add-event/AddEventPage.tsx | 18 +++++- src/pages/event/EventPage.tsx | 29 +++++----- src/pages/home/HomePage.tsx | 16 +++--- src/widgets/Header/index.ts | 2 + src/widgets/Header/ui/Header.tsx | 37 ++++++++++-- src/widgets/Header/ui/HeaderLoader.tsx | 22 +++++++ src/widgets/Header/ui/Menu.tsx | 2 +- src/widgets/Header/ui/ProfileButton.tsx | 28 +++++++++ src/widgets/ProfileButton/ProfileButton.tsx | 18 ------ src/widgets/ProfileButton/index.ts | 1 - src/widgets/eventPage/ui/CreatorDetails.tsx | 10 +++- 29 files changed, 337 insertions(+), 212 deletions(-) delete mode 100644 src/app/guards/AuthGuard.tsx create mode 100644 src/app/guards/RouteGuard.tsx delete mode 100644 src/features/authentication/logout/index.ts delete mode 100644 src/features/authentication/logout/ui/LogoutButton.tsx delete mode 100644 src/features/burgerMenu/model/types.ts create mode 100644 src/widgets/Header/ui/HeaderLoader.tsx create mode 100644 src/widgets/Header/ui/ProfileButton.tsx delete mode 100644 src/widgets/ProfileButton/ProfileButton.tsx diff --git a/src/app/appRouter.tsx b/src/app/appRouter.tsx index 52f7cc89..1b73d9a0 100644 --- a/src/app/appRouter.tsx +++ b/src/app/appRouter.tsx @@ -13,34 +13,22 @@ import RemoteProfileView from "@/pages/profile/RemoteProfileView" import EditProfile from '@/pages/profile/EditProfile'; import SecurityPage from '@/pages/security/SecurityPage'; import ProxyConfirmEmailPage from '@/features/authentication/registration/ui/ProxyConfirmEmailPage'; -import AuthGuard from './guards/AuthGuard'; +import RouteGuard from './guards/RouteGuard'; -// interface GuestGuardProps { -// children: ReactElement -// } - -// function GuestGuard({children}: GuestGuardProps) { -// if (!selectAccessToken()) return - -// return children -// } - -const appRouter = createBrowserRouter([ +const appRouter = createBrowserRouter([ { - element: , + element: , errorElement:
error
, children: [ { - path: '/', + path: '/security', element: ( - - ), + + ) }, { - path: '/events/:eventId', - element: ( - - ) + path: '/security/email/confirm', + element: }, { path: '/events/:eventId/edit', @@ -71,27 +59,35 @@ const appRouter = createBrowserRouter([ element: ( ), - }, + } + ] + }, + { + element: , + errorElement:
error
, + children: [ { - path: '*', + path: '/', element: ( - + ), }, { - path: '/security', + path: '/events/:eventId', element: ( - + ) }, { - path: '/security/email/confirm', - element: + path: '*', + element: ( + + ), } ] }, { - element: , + element: , errorElement:
error
, children: [ { diff --git a/src/app/guards/AuthGuard.tsx b/src/app/guards/AuthGuard.tsx deleted file mode 100644 index f937141d..00000000 --- a/src/app/guards/AuthGuard.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { selectAccessToken } from "@/shared/lib"; -import { ReactElement } from "react" -import { Navigate } from "react-router-dom"; - -interface AuthGuardProps { - children: ReactElement; -} - -function AuthGuard({children}: AuthGuardProps) { - if (selectAccessToken()) return - - return children -} - -export default AuthGuard; diff --git a/src/app/guards/RouteGuard.tsx b/src/app/guards/RouteGuard.tsx new file mode 100644 index 00000000..d6d5c559 --- /dev/null +++ b/src/app/guards/RouteGuard.tsx @@ -0,0 +1,33 @@ +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; +import { ReactElement } from "react"; +import { Navigate } from "react-router-dom"; + +interface GuestGuardProps { + children: ReactElement; + type: 'auth' | 'guest'; +} + +function RouteGuard({ children, type }: GuestGuardProps) { + const { + isError, + isSuccess + } = useMyDetailsQuery(); + + if (isError && type === 'guest') { + return + } + + if (isError && type === 'auth') { + return children; + } + + if (isSuccess && type === 'auth') { + return + } + + if (isSuccess) { + return children; + } +} + +export default RouteGuard; diff --git a/src/app/rootReducer.ts b/src/app/rootReducer.ts index c5c86562..db5def44 100644 --- a/src/app/rootReducer.ts +++ b/src/app/rootReducer.ts @@ -1,6 +1,6 @@ import {combineReducers} from '@reduxjs/toolkit'; import {baseApi, jwtApi} from '@/shared/api'; -import {SessionSlice} from "@/entities/session/model/slice"; +import SessionSlice from "@/entities/session/model/slice"; import {registerFormSlice} from '@/features/authentication/registration/model/formState'; import { searchFilterSlice } from '@/features/searchFilter/model/SearchFilterSlice'; import addressControlSlice from '@/features/addressControl/model/addressControlSlice'; diff --git a/src/entities/event/lib/useEventActions.ts b/src/entities/event/lib/useEventActions.ts index 12630392..1fcaa7ec 100644 --- a/src/entities/event/lib/useEventActions.ts +++ b/src/entities/event/lib/useEventActions.ts @@ -1,6 +1,7 @@ import { useLeaveFromEventMutation, useLikeEventMutation, useRegisterToEventMutation, useUnlikeEventMutation } from "@/entities/event/api/eventApi"; import { useAppDispatch } from "@/shared/model"; import { isFavoriteSetted, isParticipantSetted } from "../model/eventInfoSlice"; +import { useCallback } from "react"; export function useEventActions(eventId: number) { const dispatch = useAppDispatch(); @@ -11,41 +12,49 @@ export function useEventActions(eventId: number) { // In this responses I specifically change the state of the button without waiting // for response from the server to prevent delay when clicking - const handleRegisterToEvent = () => { + const handleRegisterToEvent = useCallback(async () => { dispatch(isParticipantSetted(true)); - registerToEvent(eventId) - .unwrap() - .then(() => {return}) - .catch(() => dispatch(isParticipantSetted(false))); - } + try { + await registerToEvent(eventId).unwrap(); + } catch (err) { + dispatch(isParticipantSetted(false)); + console.log(err); + } + }, [dispatch, eventId, registerToEvent]); - const handleLeaveFromEvent = () => { + const handleLeaveFromEvent = useCallback(async () => { dispatch(isParticipantSetted(false)); - leaveFromEvent(eventId) - .unwrap() - .then(() => {return}) - .catch(() => dispatch(isParticipantSetted(true))); - } + try { + await leaveFromEvent(eventId).unwrap(); + } catch (err) { + dispatch(isParticipantSetted(false)); + console.log(err); + } + }, [dispatch, eventId, leaveFromEvent]); - const handleLikeEvent = () => { + const handleLikeEvent = useCallback(async () => { dispatch(isFavoriteSetted(true)); - likeEvent(eventId) - .unwrap() - .then(() => {return}) - .catch(() => dispatch(isFavoriteSetted(false))); - } + try { + await likeEvent(eventId).unwrap() + } catch (err) { + dispatch(isFavoriteSetted(false)); + console.log(err); + } + }, [dispatch, eventId, likeEvent]); - const handleUnlikeEvent = () => { + const handleUnlikeEvent = useCallback(async () => { dispatch(isFavoriteSetted(false)); - unlikeEvent(eventId) - .unwrap() - .then(() => {return}) - .catch(() => dispatch(isFavoriteSetted(true))); - } + try { + await unlikeEvent(eventId).unwrap() + } catch (err) { + dispatch(isFavoriteSetted(true)); + console.log(err); + } + }, [dispatch, eventId, unlikeEvent]); return { handleRegisterToEvent, handleLeaveFromEvent, handleLikeEvent, handleUnlikeEvent }; } diff --git a/src/entities/event/model/types.ts b/src/entities/event/model/types.ts index 3b8a85be..df84bf23 100644 --- a/src/entities/event/model/types.ts +++ b/src/entities/event/model/types.ts @@ -31,7 +31,6 @@ export interface IEvent { export interface IGetEventRequest { search?: string; - category__name__in?: string; ordering?: 'start_date' | 'average_rating' | 'participants_number' | '-start_date' | '-average_rating' | '-participants_number'; name?: string; name_contains?: string; diff --git a/src/entities/event/ui/EventCard.tsx b/src/entities/event/ui/EventCard.tsx index f3adf508..0d2a4028 100644 --- a/src/entities/event/ui/EventCard.tsx +++ b/src/entities/event/ui/EventCard.tsx @@ -1,8 +1,9 @@ -import {ReactElement, useState} from "react"; +import {ReactElement, useEffect, useState} from "react"; import {IEvent} from "../model/types"; import { Link } from "react-router-dom"; import Svg from "@/shared/ui/Svg"; import { useLikeEventMutation, useUnlikeEventMutation } from "../api/eventApi"; +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; export interface IEventCard { event: IEvent; @@ -11,6 +12,10 @@ export interface IEventCard { export function EventCard({ event }: IEventCard): ReactElement { const [isFavorite, setIsFavorite] = useState(event.is_favorite); + const { + isError + } = useMyDetailsQuery(); + const [likeEvent] = useLikeEventMutation(); const [unlikeEvent] = useUnlikeEventMutation(); @@ -32,6 +37,12 @@ export function EventCard({ event }: IEventCard): ReactElement { .catch(() => setIsFavorite(true)); } + useEffect(() => { + if (isError) { + setIsFavorite(false); + } + }, [isError]); + return (
diff --git a/src/entities/eventParticipants/ui/ParticipantCard.tsx b/src/entities/eventParticipants/ui/ParticipantCard.tsx index d76c1b7f..2f543a6d 100644 --- a/src/entities/eventParticipants/ui/ParticipantCard.tsx +++ b/src/entities/eventParticipants/ui/ParticipantCard.tsx @@ -1,16 +1,17 @@ import { config } from "@/shared/config"; -import { ReactElement } from "react"; +import { ReactElement, useState } from "react"; import { IParticipant } from "../model/types"; import { IconButton } from "@/shared"; import { useNavigate } from "react-router-dom"; +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; +import { skipToken } from "@reduxjs/toolkit/query"; interface IParticipantCardProps { participant: IParticipant; isCurrentUserCard: boolean; isOwnerCard: boolean; isOwnerView: boolean; - handleKickParticipant: (userId: number) => void; - isKickLoading: boolean; + handleKickParticipant: (userId: number) => Promise; } function ParticipantCard({ @@ -18,21 +19,35 @@ function ParticipantCard({ isCurrentUserCard, isOwnerCard, isOwnerView, - handleKickParticipant, - isKickLoading + handleKickParticipant }: IParticipantCardProps): ReactElement { const navigate = useNavigate(); + const [isButtonDisabled, setIsButtonDisabled] = useState(false); + + const { + isSuccess: isProfileSuccess + } = useMyDetailsQuery(skipToken); + + const onKick = (user_id: number) => { + setIsButtonDisabled(true); + + handleKickParticipant(user_id) + .then(() => { + setIsButtonDisabled(false); + }) + .catch((err) => console.log(err)); + } return (
navigate(`/profile/${participant.id}`)} + onClick={isProfileSuccess ? () => navigate(`/profile/${participant.id}`) : undefined} src={`${config.BASE_IMAGE_URL}${participant.image_url}`} className="w-[50px] h-[50px] rounded-circle cursor-pointer" alt={`Аватар пользователя ${participant.username}`} />
navigate(`/profile/${participant.id}`)} + onClick={isProfileSuccess ? () => navigate(`/profile/${participant.id}`) : undefined} className="ml-3 text-[18px] font-medium max-w-[300px] truncate cursor-pointer" >{participant.username}
{ @@ -42,12 +57,12 @@ function ParticipantCard({

Вы

) : ( handleKickParticipant(participant.id) : undefined} + onClick={isOwnerView ? () => onKick(participant.id) : undefined} iconId={isOwnerView ? "delete-person-icon" : "add-person-icon"} size="lg" importance={isOwnerView ? "primary-opposite" : "primary"} extraClass="ml-auto" - disabled={isKickLoading} + disabled={isButtonDisabled} /> ) } diff --git a/src/entities/eventParticipants/ui/ParticipantsPopup.tsx b/src/entities/eventParticipants/ui/ParticipantsPopup.tsx index 41b27e23..2c57abbd 100644 --- a/src/entities/eventParticipants/ui/ParticipantsPopup.tsx +++ b/src/entities/eventParticipants/ui/ParticipantsPopup.tsx @@ -10,7 +10,6 @@ import { Preloader } from "@/shared/ui/Preloader"; import InfiniteScroll from 'react-infinite-scroll-component'; import { useDebounce } from "use-debounce"; import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; -import { skipToken } from "@reduxjs/toolkit/query"; interface IParticipantsPopupProps { eventId: number; @@ -45,11 +44,11 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant const { data: profile, isSuccess: isProfileSuccess - } = useMyDetailsQuery(skipToken); + } = useMyDetailsQuery(); isError && console.log(`Ошибка при получении участников - ${JSON.stringify(error)}`); - const [kickParticipant, { isLoading: isKickParticipantLoading }] = useKickParticipantMutation(); + const [kickParticipant] = useKickParticipantMutation(); useEffect(() => { if (offset === 0) { @@ -67,8 +66,8 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant setInputValue(e.target.value); } - const handleKickParticipant = (user_id: number) => { - kickParticipant({ event_id: eventId, user_id }) + const handleKickParticipant = async (user_id: number): Promise => { + await kickParticipant({ event_id: eventId, user_id }) .unwrap() .then(() => { setDeletedParticipants((state) => [...state, user_id]); @@ -158,7 +157,6 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant isOwnerCard={owner.id === el.id} isOwnerView={isOwner} handleKickParticipant={handleKickParticipant} - isKickLoading={isKickParticipantLoading} /> )) } diff --git a/src/entities/profile/api/profileApi.ts b/src/entities/profile/api/profileApi.ts index edeb27a9..29461bb3 100644 --- a/src/entities/profile/api/profileApi.ts +++ b/src/entities/profile/api/profileApi.ts @@ -17,6 +17,7 @@ export const profileApi = baseApi.injectEndpoints({ }), transformResponse: (response: ProfileDetailsDto) => mapProfileDetails(response), + providesTags: ['SESSION_TAG'] }), getFollowing: build.query({ query: ({userId}) => ({ diff --git a/src/entities/session/api/sessionApi.ts b/src/entities/session/api/sessionApi.ts index 3d4181ea..be26c1dd 100644 --- a/src/entities/session/api/sessionApi.ts +++ b/src/entities/session/api/sessionApi.ts @@ -1,4 +1,4 @@ -import {baseApi, SESSION_TAG} from '@/shared/api' +import {baseApi, EVENTS_TAG, PARTICIPANTS_TAG, PROFILE_TAG, SESSION_TAG} from '@/shared/api' import {mapSession} from '@/shared/lib/mapSession' import {RefreshToken, Session, SessionDto} from "@/shared/model/types"; import {RequestEmailCheckBody, RequestLoginBody, RequestRegistrationBody} from '../model/types'; @@ -12,7 +12,7 @@ export const sessionApi = baseApi.injectEndpoints({ method: 'POST', body, }), - invalidatesTags: [SESSION_TAG], + invalidatesTags: [SESSION_TAG, EVENTS_TAG], transformResponse: (response: SessionDto) => mapSession(response), }), logout: build.mutation({ @@ -21,7 +21,7 @@ export const sessionApi = baseApi.injectEndpoints({ method: 'POST', body, }), - invalidatesTags: [SESSION_TAG], + invalidatesTags: [SESSION_TAG, EVENTS_TAG, PARTICIPANTS_TAG, PROFILE_TAG], }), register: build.mutation({ query: (body) => ({ diff --git a/src/entities/session/model/slice.ts b/src/entities/session/model/slice.ts index c036bd71..d55f9080 100644 --- a/src/entities/session/model/slice.ts +++ b/src/entities/session/model/slice.ts @@ -2,12 +2,10 @@ import {createSlice} from '@reduxjs/toolkit' import {jwtApi} from "@/shared/api"; import {sessionApi} from "@/entities/session"; - -export const SessionSlice = createSlice({ +const SessionSlice = createSlice({ name: 'base', initialState: {}, - reducers: { - }, + reducers: {}, extraReducers: (builder) => { builder.addMatcher( sessionApi.endpoints.login.matchFulfilled, @@ -37,3 +35,5 @@ export const SessionSlice = createSlice({ ) }, }) + +export default SessionSlice; diff --git a/src/features/authentication/logout/index.ts b/src/features/authentication/logout/index.ts deleted file mode 100644 index a979a058..00000000 --- a/src/features/authentication/logout/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LogoutButton } from './ui/LogoutButton.tsx' diff --git a/src/features/authentication/logout/ui/LogoutButton.tsx b/src/features/authentication/logout/ui/LogoutButton.tsx deleted file mode 100644 index d5be1bab..00000000 --- a/src/features/authentication/logout/ui/LogoutButton.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { RefreshToken } from "@/shared/model/types.ts"; -import { useLogoutMutation } from "@/entities/session/api/sessionApi.ts"; -import { useNavigate } from "react-router-dom"; -import Svg from "@/shared/ui/Svg"; -import { MouseEvent } from "react"; - -export function LogoutButton(token: RefreshToken) { - const navigate = useNavigate(); - - const [logoutTrigger] = useLogoutMutation(); - - const onConfirmLogout = (e: MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - - logoutTrigger(token) - .unwrap() - .then(() => navigate("/")) - .catch((error) => console.log(error)); - }; - - return ( -
-
- -
-

- Выйти из профиля -

-
- ); -} diff --git a/src/features/burgerMenu/model/types.ts b/src/features/burgerMenu/model/types.ts deleted file mode 100644 index ec356dec..00000000 --- a/src/features/burgerMenu/model/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IItem { - img: string; - name: string; - link: string - } \ No newline at end of file diff --git a/src/features/burgerMenu/ui/burgerMenu.tsx b/src/features/burgerMenu/ui/burgerMenu.tsx index 0e26b8c8..4751041f 100644 --- a/src/features/burgerMenu/ui/burgerMenu.tsx +++ b/src/features/burgerMenu/ui/burgerMenu.tsx @@ -2,20 +2,32 @@ import { Menu } from "@headlessui/react"; import { MenuItem } from "./menuItem"; import { menuItems } from "../lib/menuItems"; import { selectRefreshToken } from "@/shared/lib"; -import { LogoutButton } from "@/features/authentication/logout"; +import { useNavigate } from "react-router-dom"; +import { useLogoutMutation } from "@/entities/session/api/sessionApi"; export function BurgerMenu() { - const refresh = selectRefreshToken(); + const navigate = useNavigate(); + const menuList = menuItems.map(({ img, name, link }, id) => ( - + navigate(link)} key={id} /> )); + const [logoutTrigger] = useLogoutMutation(); + + const onConfirmLogout = () => { + if (refresh) { + logoutTrigger({ refresh }) + .unwrap() + .then(() => navigate("/")) + .catch((error) => console.log(error)); + } + }; + return ( - + {menuList} - {refresh && } + {refresh && onConfirmLogout()} />} ); } - diff --git a/src/features/burgerMenu/ui/menuItem.tsx b/src/features/burgerMenu/ui/menuItem.tsx index 891fba72..e0cc8d4b 100644 --- a/src/features/burgerMenu/ui/menuItem.tsx +++ b/src/features/burgerMenu/ui/menuItem.tsx @@ -1,25 +1,25 @@ -import { IItem } from "../model/types"; -import { Link } from "react-router-dom"; import { Menu } from "@headlessui/react"; import Svg from "@/shared/ui/Svg"; import { Fragment } from "react"; -export const MenuItem = ({ img, name, link }: IItem) => { +export interface IMenuItemProps { + img: string; + name: string; + onClick: () => void; +} + +export const MenuItem = ({ img, name, onClick }: IMenuItemProps) => { return ( - -
- -

{name}

-
- +
+ +
{name}
+
); }; - -MenuItem.displayName = 'MenuItem'; diff --git a/src/features/eventPage/ui/HeaderDetails.tsx b/src/features/eventPage/ui/HeaderDetails.tsx index dd3c11f6..e43d81af 100644 --- a/src/features/eventPage/ui/HeaderDetails.tsx +++ b/src/features/eventPage/ui/HeaderDetails.tsx @@ -5,7 +5,7 @@ import { Button } from "@/shared"; import { useAppSelector } from "@/shared/model"; import Svg from "@/shared/ui/Svg"; import { Popover } from "@headlessui/react"; -import { ReactElement, useContext } from "react"; +import { ReactElement, useContext, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; interface IHeaderDetailsProps { @@ -15,13 +15,19 @@ interface IHeaderDetailsProps { export function HeaderDetails({ event, handleOpenParticipantsPopup }: IHeaderDetailsProps): ReactElement { const navigate = useNavigate(); + const [isRegisterButtonDisabled, setIsRegisterButtonDisabled] = useState(false); const { isParticipant } = useAppSelector((state) => state.eventInfo); const { eventId } = useParams<{eventId: string}>(); const { isOwner, isFavorite } = useContext(EventPageContext); - const { handleRegisterToEvent, handleLeaveFromEvent, handleLikeEvent, handleUnlikeEvent } = useEventActions(Number(eventId)); + const { + handleRegisterToEvent, + handleLeaveFromEvent, + handleLikeEvent, + handleUnlikeEvent + } = useEventActions(Number(eventId)); const copyToClipboard = () => { navigator.clipboard.writeText(event.private_token ?? '') @@ -29,6 +35,26 @@ export function HeaderDetails({ event, handleOpenParticipantsPopup }: IHeaderDet .catch((err) => console.log(err)) } + const onRegisterToEvent = () => { + setIsRegisterButtonDisabled(true); + + handleRegisterToEvent() + .then(() => { + setIsRegisterButtonDisabled(false); + }) + .catch((err) => console.log(err)) + } + + const onLeaveFromEvent = () => { + setIsRegisterButtonDisabled(true); + + handleLeaveFromEvent() + .then(() => { + setIsRegisterButtonDisabled(false); + }) + .catch((err) => console.log(err)) + } + return (

{event.category?.name}

@@ -91,7 +117,8 @@ export function HeaderDetails({ event, handleOpenParticipantsPopup }: IHeaderDet importance={isParticipant ? 'secondary' : 'primary'} extraClass="self-start text-[18px] font-semibold mt-auto" size="md" - onClick={isParticipant ? handleLeaveFromEvent : handleRegisterToEvent} + onClick={isParticipant ? onLeaveFromEvent : onRegisterToEvent} + disabled={isRegisterButtonDisabled} >{isParticipant ? "Не смогу прийти" : "Присоединиться"} ) } diff --git a/src/pages/add-event/AddEventPage.tsx b/src/pages/add-event/AddEventPage.tsx index 93ff4d76..5af0edaf 100644 --- a/src/pages/add-event/AddEventPage.tsx +++ b/src/pages/add-event/AddEventPage.tsx @@ -10,10 +10,11 @@ import { useGetTagsQuery } from "@/entities/tags/api/tagsApi"; import { useGetCurrenciesQuery } from "@/features/addEvent/priceControl/api/currencyApi"; import { ParticipantsControl, TimeControl, MediaControl, MainInfoControl } from "@/widgets/addEvent"; import { PageTitle } from "@/widgets/PageTitle"; -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { useGetEventQuery } from "@/entities/event/api/eventApi"; import { removeExtraFields } from "@/features/addEvent/addEventForm/model/removeExtraFields"; import { defaultFormValues } from "@/features/addEvent/addEventForm/model/constants"; +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; interface IAddEventPageProps { type: 'add' | 'edit'; @@ -21,6 +22,12 @@ interface IAddEventPageProps { function AddEventPage({ type }: IAddEventPageProps): ReactElement { const { eventId } = useParams(); + const navigate = useNavigate(); + + const { + data: profile, + isSuccess: isProfileSuccess + } = useMyDetailsQuery(); const { data: categories = {results: []}, @@ -80,6 +87,15 @@ function AddEventPage({ type }: IAddEventPageProps): ReactElement { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isEventSuccess, type]); + useEffect(() => { + if (isEventSuccess && isProfileSuccess) { + if (profile.id !== event.created_by.id) { + navigate('/', { replace: true }); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEventSuccess, isProfileSuccess]); + return (
diff --git a/src/pages/event/EventPage.tsx b/src/pages/event/EventPage.tsx index e4264dd8..22c0fcb3 100644 --- a/src/pages/event/EventPage.tsx +++ b/src/pages/event/EventPage.tsx @@ -26,10 +26,7 @@ export function EventPage(): ReactElement { const { eventId } = useParams<{eventId: string}>(); const { - data: profile, - isLoading: isProfileLoading, - isError: isProfileError, - error: profileError + data: profile } = useMyDetailsQuery(); const { @@ -37,7 +34,8 @@ export function EventPage(): ReactElement { isLoading: isEventLoading, isError: isEventError, error: eventError, - isSuccess: isEventSuccess + isSuccess: isEventSuccess, + refetch } = useGetEventQuery(Number(eventId)); const { @@ -54,7 +52,6 @@ export function EventPage(): ReactElement { } = useGetReviewsQuery(Number(eventId)); isTopEventsError && console.log(`Ошибка при получении ивентов - ${JSON.stringify(topEventsError)}`); - isProfileError && console.log(`Ошибка при получении профиля - ${JSON.stringify(profileError)}`); isReviewsError && console.log(`Ошибка при получении отзывов - ${JSON.stringify(reviewsError)}`); const isOwner = event?.created_by.id === profile?.id; @@ -62,18 +59,20 @@ export function EventPage(): ReactElement { useEffect(() => { window.scrollTo(0, 0); - }, [eventId]); + setIsPageReady(false); - useEffect(() => { - if (isEventSuccess) { - dispatch(isFavoriteSetted(event.is_favorite)); - dispatch(isParticipantSetted(event.is_participant)); - setIsPageReady(true); - } + refetch() + .unwrap() + .then((res) => { + dispatch(isFavoriteSetted(res.is_favorite)); + dispatch(isParticipantSetted(res.is_participant)); + setIsPageReady(true); + }) + .catch((err) => console.log(err)) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isEventSuccess, event]); + }, [eventId]); - if (isEventLoading || isProfileLoading || isReviwesLoading || !isPageReady) { + if (isEventLoading || isReviwesLoading || !isPageReady) { return ; } diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 66c247b9..0f975334 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -10,18 +10,18 @@ import { useGetMarkersQuery } from '@/widgets/mapWidget/api/markersApi'; import { ReactElement } from 'react'; export function HomePage(): ReactElement { - const { search, checkedCategories } = useAppSelector(state => state.searchFilter); + const { search } = useAppSelector(state => state.searchFilter); const { data: events = {results: []}, - isLoading: isEventsFetching, + isLoading: isEventsLoading, isError: isEventsError, error: eventsError - } = useGetEventsQuery({ search, category__name__in: checkedCategories, ordering: 'start_date' }); + } = useGetEventsQuery({ search, ordering: 'start_date' }); const { data: topEvents = {results: []}, - isLoading: isTopEventsFetching, + isLoading: isTopEventsLoading, isError: isTopEventsError, error: topEventsError } = useGetEventsQuery({ ordering: '-average_rating' }); @@ -45,14 +45,14 @@ export function HomePage(): ReactElement { isCategoriesError && console.log(`Ошибка при получении категорий - ${JSON.stringify(categoriesError)}`); return ( -
+
- + - - + +
); } diff --git a/src/widgets/Header/index.ts b/src/widgets/Header/index.ts index 43c1c4be..c7f11fb5 100644 --- a/src/widgets/Header/index.ts +++ b/src/widgets/Header/index.ts @@ -1 +1,3 @@ export { Header } from './ui/Header'; +export { default as HeaderLoader } from './ui/HeaderLoader'; +export { ProfileButton } from './ui/ProfileButton'; diff --git a/src/widgets/Header/ui/Header.tsx b/src/widgets/Header/ui/Header.tsx index 9439a779..3dc419bd 100644 --- a/src/widgets/Header/ui/Header.tsx +++ b/src/widgets/Header/ui/Header.tsx @@ -3,24 +3,53 @@ import Logo from "./Logo"; import {ReactElement} from "react"; import { Button } from "@/shared"; import { useNavigate } from "react-router-dom"; -import { selectAccessToken } from "@/shared/lib"; import { Menu } from "./Menu"; +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; +import HeaderLoader from "./HeaderLoader"; export function Header(): ReactElement { const navigate = useNavigate(); - const access = selectAccessToken(); + + const { + isSuccess, + isLoading, + isError + } = useMyDetailsQuery(); const onLoginClick = () => { navigate('/login'); } + const onRegisterClick = () => { + navigate('/register'); + } + + if (isLoading) { + return + } + return (
- {!access && } - {access && } + {isError && ( + <> + + + + )} + {isSuccess && }
); diff --git a/src/widgets/Header/ui/HeaderLoader.tsx b/src/widgets/Header/ui/HeaderLoader.tsx new file mode 100644 index 00000000..0d11997e --- /dev/null +++ b/src/widgets/Header/ui/HeaderLoader.tsx @@ -0,0 +1,22 @@ +import SkeletonLoader from "@/shared/ui/SkeletonLoader"; +import { ReactElement } from "react" + +function HeaderLoader(): ReactElement { + return ( + + + + + + + + + ) +} + +export default HeaderLoader; diff --git a/src/widgets/Header/ui/Menu.tsx b/src/widgets/Header/ui/Menu.tsx index 876b53e3..e5257bff 100644 --- a/src/widgets/Header/ui/Menu.tsx +++ b/src/widgets/Header/ui/Menu.tsx @@ -1,7 +1,7 @@ import { Button } from "@/shared"; -import { ProfileButton } from "@/widgets/ProfileButton"; import { ReactElement } from "react"; import { useLocation, useNavigate } from "react-router-dom"; +import { ProfileButton } from "./ProfileButton"; export function Menu(): ReactElement { const navigate = useNavigate(); diff --git a/src/widgets/Header/ui/ProfileButton.tsx b/src/widgets/Header/ui/ProfileButton.tsx new file mode 100644 index 00000000..08d69074 --- /dev/null +++ b/src/widgets/Header/ui/ProfileButton.tsx @@ -0,0 +1,28 @@ +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi.ts"; +import { Menu } from "@headlessui/react"; +import { BurgerMenu } from "@/features/burgerMenu"; +import { Fragment } from "react"; + +export function ProfileButton() { + const { + data: profileData, + isSuccess + } = useMyDetailsQuery(); + + return ( + +
+ + {isSuccess && ( + {`Аватар + ) } + + +
+
+ ); +} diff --git a/src/widgets/ProfileButton/ProfileButton.tsx b/src/widgets/ProfileButton/ProfileButton.tsx deleted file mode 100644 index 186b4a38..00000000 --- a/src/widgets/ProfileButton/ProfileButton.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { useMyDetailsQuery } from "@/entities/profile/api/profileApi.ts"; -import { Menu } from "@headlessui/react"; -import { BurgerMenu } from "@/features/burgerMenu"; - -export function ProfileButton() { - const { data: profileData } = useMyDetailsQuery(); - - return ( - -
- - {profileData && {`Аватар } - - -
-
- ); -} diff --git a/src/widgets/ProfileButton/index.ts b/src/widgets/ProfileButton/index.ts index c6b07f24..6d4e3909 100644 --- a/src/widgets/ProfileButton/index.ts +++ b/src/widgets/ProfileButton/index.ts @@ -1,2 +1 @@ -export { ProfileButton } from "./ProfileButton.tsx"; export { ProfileFollowButton } from "./ProfileFollowButton.tsx"; diff --git a/src/widgets/eventPage/ui/CreatorDetails.tsx b/src/widgets/eventPage/ui/CreatorDetails.tsx index 40508912..cbeefc23 100644 --- a/src/widgets/eventPage/ui/CreatorDetails.tsx +++ b/src/widgets/eventPage/ui/CreatorDetails.tsx @@ -1,8 +1,11 @@ import { IParticipant } from "@/entities/eventParticipants/model/types"; +import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; import { EventPageContext } from "@/pages/event/model/EventPageContext"; import { Button } from "@/shared"; import Svg from "@/shared/ui/Svg"; +import { skipToken } from "@reduxjs/toolkit/query"; import { ReactElement, useContext } from "react"; +import { useNavigate } from "react-router-dom"; interface ICreatorDetails { creator: IParticipant; @@ -10,6 +13,11 @@ interface ICreatorDetails { export function CreatorDetails({creator}: ICreatorDetails): ReactElement { const { isOwner } = useContext(EventPageContext); + const navigate = useNavigate(); + + const { + isSuccess: isProfileSuccess + } = useMyDetailsQuery(skipToken); return (
@@ -19,7 +27,7 @@ export function CreatorDetails({creator}: ICreatorDetails): ReactElement {
{`Аватар
- {creator.username} +

navigate(`/profile/${creator.id}`) : undefined} className="underline text-[24px] font-semibold">{creator.username}

{creator.bio ?? "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsumhasbeentheindustry'sstandardummytexteversincethe1500s,whenanunknownprintertookagalleyoftypeand scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."}

{ !isOwner && ( From 0fdd3f9f971ffab76241ca50fcacb1fe3c19e42f Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 25 May 2024 00:31:15 +0300 Subject: [PATCH 2/4] fix: lint check fix --- src/features/eventPage/ui/HeaderDetails.tsx | 1 + src/widgets/eventPage/ui/Location.tsx | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/features/eventPage/ui/HeaderDetails.tsx b/src/features/eventPage/ui/HeaderDetails.tsx index e43d81af..eda20c11 100644 --- a/src/features/eventPage/ui/HeaderDetails.tsx +++ b/src/features/eventPage/ui/HeaderDetails.tsx @@ -63,6 +63,7 @@ export function HeaderDetails({ event, handleOpenParticipantsPopup }: IHeaderDet id="heart-icon" extraUseClass={isFavorite ? "!fill-but-primary stroke-but-primary" : "stroke-text-black"} className="absolute top-[76px] right-[30px] w-6 h-6 cursor-pointer duration-150 hoverscreen:hover:opacity-70" + // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick={isFavorite ? handleUnlikeEvent : handleLikeEvent} />
diff --git a/src/widgets/eventPage/ui/Location.tsx b/src/widgets/eventPage/ui/Location.tsx index dd5f5833..74376bbe 100644 --- a/src/widgets/eventPage/ui/Location.tsx +++ b/src/widgets/eventPage/ui/Location.tsx @@ -17,7 +17,12 @@ export function Location({event}: ILocationProps): ReactElement { const { isParticipant } = useAppSelector((state) => state.eventInfo); const { eventId } = useParams<{eventId: string}>(); - const { handleRegisterToEvent, handleLeaveFromEvent, handleLikeEvent, handleUnlikeEvent } = useEventActions(Number(eventId)); + const { + handleRegisterToEvent, + handleLeaveFromEvent, + handleLikeEvent, + handleUnlikeEvent + } = useEventActions(Number(eventId)); return (
@@ -41,6 +46,7 @@ export function Location({event}: ILocationProps): ReactElement { id="heart-icon" extraUseClass={`stroke-white ${isFavorite ? "!fill-white" : ""}`} className="w-6 h-6 ml-5 cursor-pointer duration-150 hoverscreen:hover:opacity-70" + // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick={isFavorite ? handleUnlikeEvent : handleLikeEvent} />
From 3b555cf47c14a496670870297e1e2cdf2fe71cf7 Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 25 May 2024 18:56:45 +0300 Subject: [PATCH 3/4] fix: fix categories filter on homepage --- src/features/searchFilter/model/SearchFilterSlice.ts | 6 +++--- src/features/searchFilter/ui/FilterPopup.tsx | 12 ++++++------ src/pages/home/HomePage.tsx | 6 ++++-- src/shared/ui/CheckboxWithLabel.tsx | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/features/searchFilter/model/SearchFilterSlice.ts b/src/features/searchFilter/model/SearchFilterSlice.ts index 0f13e04c..ec63dcd7 100644 --- a/src/features/searchFilter/model/SearchFilterSlice.ts +++ b/src/features/searchFilter/model/SearchFilterSlice.ts @@ -2,12 +2,12 @@ import { createSlice } from "@reduxjs/toolkit"; interface IGlobalFilterState { search: string; - checkedCategories: string; + checkedCategories: number[]; } const initialState: IGlobalFilterState = { search: '', - checkedCategories: '' + checkedCategories: [] } export const searchFilterSlice = createSlice({ @@ -17,7 +17,7 @@ export const searchFilterSlice = createSlice({ setSearchFilter: (state, { payload: inputValue }: { payload: string }) => ({ ...state, search: inputValue, }), - categorySetted: (state, { payload: checkedCategories }: { payload: string }) => ({ + categorySetted: (state, { payload: checkedCategories }: { payload: number[] }) => ({ ...state, checkedCategories }) } diff --git a/src/features/searchFilter/ui/FilterPopup.tsx b/src/features/searchFilter/ui/FilterPopup.tsx index 26e2edf7..6b669305 100644 --- a/src/features/searchFilter/ui/FilterPopup.tsx +++ b/src/features/searchFilter/ui/FilterPopup.tsx @@ -13,20 +13,19 @@ interface IFilterPopupProps { export function FilterPopup({ categories }: IFilterPopupProps): ReactElement { const dispatch = useAppDispatch(); const { isOpen } = useAppSelector((state) => state.filterPopup); - const [checkedCategories, setCheckedCategories] = useState([]); + const { checkedCategories } = useAppSelector((state) => state.searchFilter); + const [checkedCategoriesArr, setCheckedCategoriesArr] = useState(checkedCategories || []); const handleCheckedCategories = (e: ChangeEvent, category: ICategory) => { e.target.checked - ? setCheckedCategories((state) => ([...state, category])) - : setCheckedCategories((state) => state.filter((el) => el.id !== category.id)); + ? setCheckedCategoriesArr((state) => ([...state, category.id])) + : setCheckedCategoriesArr((state) => state.filter((el) => el !== category.id)); } const onButtonClick = () => { dispatch(isPopupOpenSetted(false)); - const checkedCategoriesString = checkedCategories.map((el) => el.name).join(','); - - dispatch(categorySetted(checkedCategoriesString)); + dispatch(categorySetted(checkedCategoriesArr)); } return ( @@ -49,6 +48,7 @@ export function FilterPopup({ categories }: IFilterPopupProps): ReactElement { extraBoxClass="mt-3 first-of-type:mt-4" extraLabelClass="ml-2" onChange={(e: ChangeEvent) => handleCheckedCategories(e, category)} + value={checkedCategories.some((el) => el === category.id)} /> ) } diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 0f975334..c018b774 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -10,14 +10,16 @@ import { useGetMarkersQuery } from '@/widgets/mapWidget/api/markersApi'; import { ReactElement } from 'react'; export function HomePage(): ReactElement { - const { search } = useAppSelector(state => state.searchFilter); + const { search, checkedCategories } = useAppSelector(state => state.searchFilter); + + const category_in = checkedCategories.join(','); const { data: events = {results: []}, isLoading: isEventsLoading, isError: isEventsError, error: eventsError - } = useGetEventsQuery({ search, ordering: 'start_date' }); + } = useGetEventsQuery({ search, category_in, ordering: 'start_date' }); const { data: topEvents = {results: []}, diff --git a/src/shared/ui/CheckboxWithLabel.tsx b/src/shared/ui/CheckboxWithLabel.tsx index a40799b5..70a30a6e 100644 --- a/src/shared/ui/CheckboxWithLabel.tsx +++ b/src/shared/ui/CheckboxWithLabel.tsx @@ -10,7 +10,7 @@ interface ICheckboxWithLabelProps { } export function CheckboxWithLabel({ id, label, value, onChange, extraBoxClass, extraLabelClass }: ICheckboxWithLabelProps): ReactElement { - const [isChecked, setIsChecked] = useState(false); + const [isChecked, setIsChecked] = useState(value ?? false); const handleChecked = (e: ChangeEvent) => { setIsChecked(e.target.checked); @@ -20,7 +20,7 @@ export function CheckboxWithLabel({ id, label, value, onChange, extraBoxClass, e return (
- +
) From c7c5dba0381e073b05b76c74997f91cc54ef3e6a Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 31 May 2024 22:06:38 +0300 Subject: [PATCH 4/4] fix: remove useless profile server responses and some fixes --- src/entities/event/ui/EventCard.tsx | 11 ++-------- .../eventParticipants/ui/ParticipantCard.tsx | 3 +-- .../ui/ParticipantsPopup.tsx | 3 ++- src/entities/profile/api/profileApi.ts | 20 ++++++++++++++----- src/entities/session/model/slice.ts | 19 +++++++++++++++++- src/features/eventChoice/ui/EventSlider.tsx | 4 ++-- src/pages/event/EventPage.tsx | 3 ++- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/entities/event/ui/EventCard.tsx b/src/entities/event/ui/EventCard.tsx index 0d2a4028..d552344c 100644 --- a/src/entities/event/ui/EventCard.tsx +++ b/src/entities/event/ui/EventCard.tsx @@ -3,7 +3,6 @@ import {IEvent} from "../model/types"; import { Link } from "react-router-dom"; import Svg from "@/shared/ui/Svg"; import { useLikeEventMutation, useUnlikeEventMutation } from "../api/eventApi"; -import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; export interface IEventCard { event: IEvent; @@ -12,10 +11,6 @@ export interface IEventCard { export function EventCard({ event }: IEventCard): ReactElement { const [isFavorite, setIsFavorite] = useState(event.is_favorite); - const { - isError - } = useMyDetailsQuery(); - const [likeEvent] = useLikeEventMutation(); const [unlikeEvent] = useUnlikeEventMutation(); @@ -38,10 +33,8 @@ export function EventCard({ event }: IEventCard): ReactElement { } useEffect(() => { - if (isError) { - setIsFavorite(false); - } - }, [isError]); + setIsFavorite(event.is_favorite); + }, [event]) return (
diff --git a/src/entities/eventParticipants/ui/ParticipantCard.tsx b/src/entities/eventParticipants/ui/ParticipantCard.tsx index 2f543a6d..bc5e259c 100644 --- a/src/entities/eventParticipants/ui/ParticipantCard.tsx +++ b/src/entities/eventParticipants/ui/ParticipantCard.tsx @@ -4,7 +4,6 @@ import { IParticipant } from "../model/types"; import { IconButton } from "@/shared"; import { useNavigate } from "react-router-dom"; import { useMyDetailsQuery } from "@/entities/profile/api/profileApi"; -import { skipToken } from "@reduxjs/toolkit/query"; interface IParticipantCardProps { participant: IParticipant; @@ -26,7 +25,7 @@ function ParticipantCard({ const { isSuccess: isProfileSuccess - } = useMyDetailsQuery(skipToken); + } = useMyDetailsQuery(); const onKick = (user_id: number) => { setIsButtonDisabled(true); diff --git a/src/entities/eventParticipants/ui/ParticipantsPopup.tsx b/src/entities/eventParticipants/ui/ParticipantsPopup.tsx index 2c57abbd..bcca4bae 100644 --- a/src/entities/eventParticipants/ui/ParticipantsPopup.tsx +++ b/src/entities/eventParticipants/ui/ParticipantsPopup.tsx @@ -22,6 +22,7 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant const [inputValue, setInputValue] = useState(''); const [offset, setOffset] = useState(0); const [deletedParticipants, setDeletedParticipants] = useState([]); + const { isAuthorized } = useAppSelector((state) => state.session); const { isParticipant } = useAppSelector((state) => state.eventInfo); const { isOwner } = useContext(EventPageContext); @@ -44,7 +45,7 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant const { data: profile, isSuccess: isProfileSuccess - } = useMyDetailsQuery(); + } = useMyDetailsQuery(undefined, { skip: !isAuthorized }); isError && console.log(`Ошибка при получении участников - ${JSON.stringify(error)}`); diff --git a/src/entities/profile/api/profileApi.ts b/src/entities/profile/api/profileApi.ts index 29461bb3..72b293b1 100644 --- a/src/entities/profile/api/profileApi.ts +++ b/src/entities/profile/api/profileApi.ts @@ -1,6 +1,7 @@ import {baseApi} from '@/shared/api' import {ProfileDetails, ProfileId, ProfileFollowing, IFollowResponse, ProfileDetailsDto} from "@/entities/profile/model/types"; import {mapProfileDetails} from "@/entities/profile/lib/mapProfileDetails"; +import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; export const profileApi = baseApi.injectEndpoints({ endpoints: (build) => ({ @@ -12,11 +13,20 @@ export const profileApi = baseApi.injectEndpoints({ mapProfileDetails(response), }), myDetails: build.query({ - query: () => ({ - url: `/me`, - }), - transformResponse: (response: ProfileDetailsDto) => - mapProfileDetails(response), + queryFn: async (arg, api, extraOptions, baseQuery) => { + const result = await baseQuery({ + url: `/me`, + method: 'GET' + }); + + if (result.error?.status === 401) { + return { error: result.error.data as FetchBaseQueryError }; + } + + const mappedData = mapProfileDetails(result.data as ProfileDetailsDto); + + return { data: mappedData }; + }, providesTags: ['SESSION_TAG'] }), getFollowing: build.query({ diff --git a/src/entities/session/model/slice.ts b/src/entities/session/model/slice.ts index d55f9080..3f73eb54 100644 --- a/src/entities/session/model/slice.ts +++ b/src/entities/session/model/slice.ts @@ -1,10 +1,15 @@ import {createSlice} from '@reduxjs/toolkit' import {jwtApi} from "@/shared/api"; import {sessionApi} from "@/entities/session"; +import { profileApi } from '@/entities/profile'; + +const initialState = { + isAuthorized: false +} const SessionSlice = createSlice({ name: 'base', - initialState: {}, + initialState, reducers: {}, extraReducers: (builder) => { builder.addMatcher( @@ -32,6 +37,18 @@ const SessionSlice = createSlice({ (state, { payload }) => { localStorage.setItem('access-token', payload.access) } + ), + builder.addMatcher( + profileApi.endpoints.myDetails.matchRejected, + () => ({ + isAuthorized: false + }) + ), + builder.addMatcher( + profileApi.endpoints.myDetails.matchFulfilled, + () => ({ + isAuthorized: true + }) ) }, }) diff --git a/src/features/eventChoice/ui/EventSlider.tsx b/src/features/eventChoice/ui/EventSlider.tsx index f154f648..9cbfb0b7 100644 --- a/src/features/eventChoice/ui/EventSlider.tsx +++ b/src/features/eventChoice/ui/EventSlider.tsx @@ -8,7 +8,7 @@ interface IEventSlider { } export function EventSlider({ events }: IEventSlider): ReactElement { - const cards = events.map((el, index) => ); + const cards = events.map((el) => ); const [width, setWidth] = useState(103.7); const [slidesToShow, setSlidesToShow] = useState(4); @@ -45,4 +45,4 @@ export function EventSlider({ events }: IEventSlider): ReactElement { {cards} ) -} \ No newline at end of file +} diff --git a/src/pages/event/EventPage.tsx b/src/pages/event/EventPage.tsx index 22c0fcb3..87ea9829 100644 --- a/src/pages/event/EventPage.tsx +++ b/src/pages/event/EventPage.tsx @@ -24,10 +24,11 @@ export function EventPage(): ReactElement { const dispatch = useAppDispatch(); const [isParticipantPopupOpen, setIsParticipantPopupOpen] = useState(false); const { eventId } = useParams<{eventId: string}>(); + const { isAuthorized } = useAppSelector((state) => state.session); const { data: profile - } = useMyDetailsQuery(); + } = useMyDetailsQuery(undefined, { skip: !isAuthorized }); const { data: event,