Skip to content

Commit

Permalink
Merge pull request #63 from jakkemomo/general_fixes
Browse files Browse the repository at this point in the history
fix: add RouteGuard, change route-access logic and some general fixes
  • Loading branch information
algoritmi4 authored Jun 1, 2024
2 parents d9e57c3 + 7bb0cd4 commit 91c5e4c
Show file tree
Hide file tree
Showing 32 changed files with 370 additions and 208 deletions.
52 changes: 24 additions & 28 deletions src/app/appRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Navigate to="/login"/>

// return children
// }

const appRouter = createBrowserRouter([
const appRouter = createBrowserRouter([
{
element: <BaseLayout />,
element: <RouteGuard type="guest"><BaseLayout /></RouteGuard>,
errorElement: <div>error</div>,
children: [
{
path: '/',
path: '/security',
element: (
<HomePage/>
),
<SecurityPage />
)
},
{
path: '/events/:eventId',
element: (
<EventPage/>
)
path: '/security/email/confirm',
element: <ProxyConfirmEmailPage type='security' />
},
{
path: '/events/:eventId/edit',
Expand Down Expand Up @@ -71,27 +59,35 @@ const appRouter = createBrowserRouter([
element: (
<AddEventPage type='add' />
),
},
}
]
},
{
element: <BaseLayout />,
errorElement: <div>error</div>,
children: [
{
path: '*',
path: '/',
element: (
<NonFound/>
<HomePage/>
),
},
{
path: '/security',
path: '/events/:eventId',
element: (
<SecurityPage />
<EventPage/>
)
},
{
path: '/security/email/confirm',
element: <ProxyConfirmEmailPage type='security' />
path: '*',
element: (
<NonFound/>
),
}
]
},
{
element: <AuthGuard><AuthLayout/></AuthGuard>,
element: <RouteGuard type="auth"><AuthLayout/></RouteGuard>,
errorElement: <div>error</div>,
children: [
{
Expand Down
15 changes: 0 additions & 15 deletions src/app/guards/AuthGuard.tsx

This file was deleted.

33 changes: 33 additions & 0 deletions src/app/guards/RouteGuard.tsx
Original file line number Diff line number Diff line change
@@ -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 <Navigate to="/" replace />
}

if (isError && type === 'auth') {
return children;
}

if (isSuccess && type === 'auth') {
return <Navigate to="/" replace />
}

if (isSuccess) {
return children;
}
}

export default RouteGuard;
2 changes: 1 addition & 1 deletion src/app/rootReducer.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
57 changes: 33 additions & 24 deletions src/entities/event/lib/useEventActions.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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 };
}
1 change: 0 additions & 1 deletion src/entities/event/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion src/entities/event/ui/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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";
Expand Down Expand Up @@ -32,6 +32,10 @@ export function EventCard({ event }: IEventCard): ReactElement {
.catch(() => setIsFavorite(true));
}

useEffect(() => {
setIsFavorite(event.is_favorite);
}, [event])

return (
<div className="w-full flex flex-col max-w-[270px] mr-[45px]">
<div className="flex justify-between">
Expand Down
32 changes: 23 additions & 9 deletions src/entities/eventParticipants/ui/ParticipantCard.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
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";

interface IParticipantCardProps {
participant: IParticipant;
isCurrentUserCard: boolean;
isOwnerCard: boolean;
isOwnerView: boolean;
handleKickParticipant: (userId: number) => void;
isKickLoading: boolean;
handleKickParticipant: (userId: number) => Promise<void>;
}

function ParticipantCard({
participant,
isCurrentUserCard,
isOwnerCard,
isOwnerView,
handleKickParticipant,
isKickLoading
handleKickParticipant
}: IParticipantCardProps): ReactElement {
const navigate = useNavigate();
const [isButtonDisabled, setIsButtonDisabled] = useState(false);

const {
isSuccess: isProfileSuccess
} = useMyDetailsQuery();

const onKick = (user_id: number) => {
setIsButtonDisabled(true);

handleKickParticipant(user_id)
.then(() => {
setIsButtonDisabled(false);
})
.catch((err) => console.log(err));
}

return (
<figure className="flex items-center mt-3">
<img
onClick={() => 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}`}
/>
<figcaption
onClick={() => 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}</figcaption>
{
Expand All @@ -42,12 +56,12 @@ function ParticipantCard({
<p className="ml-auto font-medium text-[rgb(143,143,143)]">Вы</p>
) : (
<IconButton
onClick={isOwnerView ? () => 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}
/>
)
}
Expand Down
11 changes: 5 additions & 6 deletions src/entities/eventParticipants/ui/ParticipantsPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +22,7 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant
const [inputValue, setInputValue] = useState('');
const [offset, setOffset] = useState(0);
const [deletedParticipants, setDeletedParticipants] = useState<number[]>([]);
const { isAuthorized } = useAppSelector((state) => state.session);

const { isParticipant } = useAppSelector((state) => state.eventInfo);
const { isOwner } = useContext(EventPageContext);
Expand All @@ -45,11 +45,11 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant
const {
data: profile,
isSuccess: isProfileSuccess
} = useMyDetailsQuery(skipToken);
} = useMyDetailsQuery(undefined, { skip: !isAuthorized });

isError && console.log(`Ошибка при получении участников - ${JSON.stringify(error)}`);

const [kickParticipant, { isLoading: isKickParticipantLoading }] = useKickParticipantMutation();
const [kickParticipant] = useKickParticipantMutation();

useEffect(() => {
if (offset === 0) {
Expand All @@ -67,8 +67,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<void> => {
await kickParticipant({ event_id: eventId, user_id })
.unwrap()
.then(() => {
setDeletedParticipants((state) => [...state, user_id]);
Expand Down Expand Up @@ -158,7 +158,6 @@ function ParticipantsPopup({ eventId, owner, isOpen, handleClose }: IParticipant
isOwnerCard={owner.id === el.id}
isOwnerView={isOwner}
handleKickParticipant={handleKickParticipant}
isKickLoading={isKickParticipantLoading}
/>
))
}
Expand Down
6 changes: 3 additions & 3 deletions src/entities/session/api/sessionApi.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<void, RefreshToken>({
Expand All @@ -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<void, RequestRegistrationBody>({
query: (body) => ({
Expand Down
Loading

0 comments on commit 91c5e4c

Please sign in to comment.