Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Feature Commit #6

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
35 changes: 31 additions & 4 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { useSelector } from 'react-redux';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import { themeSettings } from 'theme';
import HomePage from 'scenes/homePage';

import LandingPage from 'scenes/landingPage';
import ProfilePage from 'scenes/profilePage';
import EventsPage from 'scenes/eventsPage';
import HomePage from 'scenes/homePage';
import EventDetailsPage from 'scenes/eventDetailsPage';
import EventForm from 'scenes/eventForm';

const App = () => {
const mode = useSelector((state) => state.mode);
Expand All @@ -20,9 +25,31 @@ const App = () => {
<ThemeProvider theme={theme}>
<CssBaseline />
<Routes>
<Route path='/*' element={<LandingPage />} />
<Route path='/user-auth' element={<LoginPage />} />
<Route path='/home' element={isAuth ? <HomePage /> : <Navigate to='/' />} />
<Route path="/" element={<LoginPage />} />
<Route
path="/home"
element={isAuth ? <HomePage /> : <Navigate to="/" />}
/>
<Route
path="/profile/:userId"
element={isAuth ? <ProfilePage /> : <Navigate to="/" />}
/>
<Route
path="/events"
element={isAuth ? <EventsPage /> : <Navigate to="/" />}
/>
<Route
path="/events/:eventId"
element={isAuth ? <EventDetailsPage /> : <Navigate to="/" />}
/>
<Route
path="/events/create"
element={isAuth ? <EventForm /> : <Navigate to="/" />}
/>
<Route
path="/events/:eventId/edit"
element={isAuth ? <EventForm /> : <Navigate to="/" />}
/>
</Routes>
</ThemeProvider>
</BrowserRouter>
Expand Down
Binary file added client/src/assets/about.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions client/src/components/Follower.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { PersonAddOutlined, PersonRemoveOutlined } from "@mui/icons-material";
import { Box, Typography, IconButton, useTheme } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { setFollowers } from "state";
import FlexBetween from "./FlexBetween";
import UserImage from "./UserImage";

const Follower = ({ followerId, name, subtitle, userPicturePath }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { _id } = useSelector((state) => state.user);
const token = useSelector((state) => state.token);
const followers = useSelector((state) => state.user.followers);

const { palette } = useTheme();
const primaryLight = palette.primary.light;
const primaryDark = palette.primary.dark;
const main = palette.neutral.main;
const medium = palette.neutral.medium;

const isFollower = followers.find((follower) => follower._id === followerId);

const patchFollower = async () => {
const response = await fetch(
`http://localhost:3001/users/${_id}/${followerId}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
const data = await response.json();
dispatch(setFollowers({ followers: data }));
};

return (
<FlexBetween>
<FlexBetween gap="1rem">
<UserImage image={userPicturePath} size="55px" />
<Box
onClick={() => {
navigate(`/profile/${followerId}`);
navigate(0);
}}
>
<Typography
color={main}
variant="h5"
fontWeight="500"
sx={{
"&:hover": {
color: palette.primary.light,
cursor: "pointer",
},
}}
>
{name}
</Typography>
<Typography color={medium} fontSize="0.75rem">
{subtitle}
</Typography>
</Box>
</FlexBetween>
<IconButton
onClick={() => patchFollower()}
sx={{ backgroundColor: primaryLight, p: "0.6rem" }}
>
{isFollower ? (
<PersonRemoveOutlined sx={{ color: primaryDark }} />
) : (
<PersonAddOutlined sx={{ color: primaryDark }} />
)}
</IconButton>
</FlexBetween>
);
};

export default Follower;



78 changes: 78 additions & 0 deletions client/src/components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import { InputBase, IconButton, Paper, Popper, Typography, Box } from '@mui/material';
import { Search as SearchIcon } from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useSelector } from 'react-redux';

const SearchBar = () => {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const navigate = useNavigate();
const token = useSelector((state) => state.token);

const handleSearch = async (event) => {
event.preventDefault();
setAnchorEl(event.currentTarget);

try {
const response = await fetch(`http://localhost:3001/search?query=${searchTerm}`, {
method: 'GET',
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
setSearchResults(data);
} catch (error) {
console.error('Error searching:', error);
}
};

const handleResultClick = (type, id) => {
setSearchResults(null);
setSearchTerm('');
if (type === 'user') navigate(`/profile/${id}`);
else if (type === 'event') navigate(`/event/${id}`);
};

return (
<>
<Paper
component="form"
onSubmit={handleSearch}
sx={{ p: '2px 4px', display: 'flex', alignItems: 'center', width: 400 }}
>
<InputBase
sx={{ ml: 1, flex: 1 }}
placeholder="Search Astrogram"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<IconButton type="submit" sx={{ p: '10px' }} aria-label="search">
<SearchIcon />
</IconButton>
</Paper>
<Popper open={Boolean(searchResults)} anchorEl={anchorEl} placement="bottom-start">
<Paper sx={{ p: 2, maxWidth: 400 }}>
{searchResults && (
<>
{/* <Typography variant="h6">Users</Typography> */}
{searchResults.users.map((user) => (
<Box key={user._id} onClick={() => handleResultClick('user', user._id)}>
<Typography sx={{cursor: 'pointer'}}>{`${user.firstName} ${user.lastName}`}</Typography>
</Box>
))}
{/* <Typography variant="h6">Events</Typography>
{searchResults.events.map((event) => (
<Box key={event._id} onClick={() => handleResultClick('event', event._id)}>
<Typography>{event.title}</Typography>
</Box>
))} */}
</>
)}
</Paper>
</Popper>
</>
);
};

export default SearchBar;
141 changes: 141 additions & 0 deletions client/src/scenes/eventDetailsPage/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useEffect, useState } from "react";
import { Box, Typography, Button, useTheme } from "@mui/material";
import { useParams, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import Navbar from "scenes/navbar";
import WidgetWrapper from "components/WidgetWrapper";
import FlexBetween from "components/FlexBetween";
import UserImage from "components/UserImage";

const EventDetailsPage = () => {
const [event, setEvent] = useState(null);
const [isAttending, setIsAttending] = useState(false);
const { eventId } = useParams();
const token = useSelector((state) => state.token);
const user = useSelector((state) => state.user);
const navigate = useNavigate();
const { palette } = useTheme();

const getEvent = async () => {
const response = await fetch(`http://localhost:3001/events/${eventId}`, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
setEvent(data);
setIsAttending(data.attendees?.includes(user._id));
};

const handleAttendance = async () => {
const endpoint = isAttending ? 'unattend' : 'attend';
const response = await fetch(`http://localhost:3001/events/${eventId}/${endpoint}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ userId: user._id })
});
if (response.ok) {
setIsAttending(!isAttending);
getEvent(); // Refresh event data
} else {
console.error("Failed to update attendance:", await response.text());
}
};

useEffect(() => {
getEvent();
}, []);

if (!event) return null;

const isCreator = user._id === event.creatorId?._id;

return (
<Box>
<Navbar />
<Box width="100%" padding="2rem 6%" display="flex" gap="2rem" justifyContent="center">
<WidgetWrapper width="60%">
<FlexBetween>
<FlexBetween gap="1rem">
<UserImage image={event.creatorId?.picturePath || "default_avatar.png"} size="55px" />
<Box>
<Typography variant="h4" color={palette.neutral.dark} fontWeight="500">
{event.title}
</Typography>
<Typography color={palette.neutral.medium} fontSize="0.75rem">
Created by: {event.creatorId ? `${event.creatorId.firstName} ${event.creatorId.lastName}` : "Unknown"}
</Typography>
</Box>
</FlexBetween>
</FlexBetween>
<Typography color={palette.neutral.main} sx={{ mt: "1rem" }}>
{event.description}
</Typography>
<Typography color={palette.neutral.medium} sx={{ mt: "1rem" }}>
Date: {new Date(event.date).toLocaleString()}
</Typography>
<Typography color={palette.neutral.medium} sx={{ mt: "0.5rem" }}>
Location: {event.location}
</Typography>
<Typography color={palette.neutral.medium} sx={{ mt: "0.5rem" }}>
Attendees: {event.attendees?.length || 0}
</Typography>

{!isCreator && (
<Button
fullWidth
onClick={handleAttendance}
sx={{
m: "0.5rem 0",
p: "1rem",
backgroundColor: isAttending ? palette.primary.light : palette.primary.main,
color: palette.background.alt,
"&:hover": {
backgroundColor: isAttending ? palette.primary.main : palette.primary.light,
color: palette.primary.dark
},
}}
>
{isAttending ? "Attending" : "Attend Event"}
</Button>
)}

{isCreator && (
<FlexBetween mt="1rem">
<Button
onClick={() => navigate(`/events/${eventId}/edit`)}
sx={{
backgroundColor: palette.primary.main,
color: palette.background.alt,
"&:hover": { color: palette.primary.main },
}}
>
Edit Event
</Button>
<Button
onClick={async () => {
await fetch(`http://localhost:3001/events/${eventId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
navigate("/events");
}}
sx={{
backgroundColor: palette.primary.main,
color: palette.background.alt,
"&:hover": { color: palette.primary.main },
}}
>
Delete Event
</Button>
</FlexBetween>
)}
</WidgetWrapper>
</Box>
</Box>
);
};

export default EventDetailsPage;
Loading