Skip to content

Commit

Permalink
Merge pull request #328 from SCCapstone/main
Browse files Browse the repository at this point in the history
Deploy Hotfixes
  • Loading branch information
epadams authored Feb 26, 2024
2 parents 5feb2fa + c57796d commit f233c96
Show file tree
Hide file tree
Showing 17 changed files with 744 additions and 9 deletions.
2 changes: 1 addition & 1 deletion FU.API/FU.API/Controllers/PostsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task<IActionResult> UpdatePost([FromRoute] int postId, [FromBody] P
}

var post = dto.ToModel();
post.Creator = user;
post.CreatorId = user.UserId;
post.Id = postId;

post = await _postService.UpdatePost(post);
Expand Down
19 changes: 19 additions & 0 deletions FU.SPA/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion FU.SPA/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forces Unite</title>
</head>
Expand Down
22 changes: 22 additions & 0 deletions FU.SPA/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { Route, Routes } from 'react-router-dom';
import { ProtectedRoute } from './components/ProtectedRoute';
import UserProvider from './context/userProvider';
import './App.css';
import ProfileSettings from './components/pages/ProfileSettings';
import AccountSettings from './components/pages/AccountSettings';
import EditPost from './components/pages/EditPost';

function App() {
return (
Expand Down Expand Up @@ -43,11 +46,30 @@ function App() {
</ProtectedRoute>
}
/>

<Route
path="/profilesettings/"
element={
<ProtectedRoute>
<ProfileSettings />
</ProtectedRoute>
}
/>
<Route
path="/accountsettings/"
element={
<ProtectedRoute>
<AccountSettings />
</ProtectedRoute>
}
/>
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<SignIn />} />
<Route path="/posts/:postId" element={<PostPage />} />

<Route path="*" element={<NoPage />} />
<Route path="/profile/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/edit" element={<EditPost />} />
</Routes>
</div>
</UserProvider>
Expand Down
3 changes: 2 additions & 1 deletion FU.SPA/src/components/Chat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getChat, getMessages, saveMessage } from '../services/chatService';
import './Chat.css';
import ChatMessage from './ChatMessage';
import UserContext from '../context/userContext';
import config from '../config';

export default function Chat({ chatId }) {
const [chat, setChat] = useState(null);
Expand All @@ -33,7 +34,7 @@ export default function Chat({ chatId }) {
const chat = await getChat(chatId);
setChat(chat);
// See #281: We need to wait for the signalR connection to be started before joining the chat
await new Promise((resolve) => setTimeout(resolve, 80));
await new Promise((resolve) => setTimeout(resolve, config.WAIT_TIME));
await joinChatGroup(chatId);
const messages = await getMessages(chatId, 1, limit);
setMessages(messages);
Expand Down
302 changes: 302 additions & 0 deletions FU.SPA/src/components/Edit.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import {
Button,
TextField,
Box,
Container,
Typography,
Grid,
Checkbox,
Autocomplete,
createFilterOptions,
} from '@mui/material';
import { useContext, useEffect, useState } from 'react';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import PostService from '../services/postService';
import TagService from '../services/tagService';
import GameService from '../services/gameService';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import dayjs from 'dayjs';
import { useNavigate } from 'react-router-dom';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import UserContext from '../context/userContext';

export default function Edit({ postId }) {
const [game, setGame] = useState();
const [title, setTitle] = useState('');
const [startTime, setStartTime] = useState(dayjs());
const [endTime, setEndTime] = useState(dayjs().add(30, 'minute'));
const [description, setDescription] = useState('');
const [tags, setTags] = useState([]);
const navigate = useNavigate();

const { user } = useContext(UserContext);

useEffect(() => {
const init = async () => {
try {
const postDetails = await PostService.getPostDetails(postId);
if (user.id !== postDetails.creator.id) {
alert('You are not authorized to edit this post');
navigate(`/discover`);
}
} catch (e) {
console.log(e);
}
};

init();
});

const handleSubmit = async (e) => {
// change to get post state, autofill fields based on info
e.preventDefault();

let tagIds = [];

for (const tag of tags) {
const newTag = await TagService.findOrCreateTagByName(tag.name);
tagIds.push(newTag.id);
}

try {
var findGame = await GameService.findOrCreateGameByTitle(game.name);
} catch (e) {
alert(e);
console.log(e);
}

const updatedPost = {
title: title,
description: description,
startTime: startTime !== null ? startTime.toISOString() : null,
endTime: endTime !== null ? endTime.toISOString() : null,
tagIds: tagIds,
gameId: findGame.id,
};

try {
const newPost = await PostService.updatePost(updatedPost, postId);
console.log(newPost);
alert('Post updated successfully!');
navigate(`/posts/${postId}`);
} catch (e) {
window.alert(e);
console.log(e);
}
};

return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 1,
m: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography component="h1" variant="h5">
Edit Post
</Typography>
<Box
component="form"
noValidate
onSubmit={handleSubmit}
onKeyDown={(e) => {
if (e.key === 'Enter') e.preventDefault();
}}
sx={{
display: 'flex',
flexDirection: 'column',
mt: 3,
gap: 2,
}}
>
<TextField
required
fullWidth
id="searchGames"
label="Title"
autoFocus
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Grid item xs={12}>
<GameSelector onChange={setGame} />
</Grid>
<br />
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateTimePicker
label="Start Time"
value={startTime}
onChange={(newValue) => setStartTime(newValue)}
/>
<DateTimePicker
label="End Time"
value={endTime}
onChange={(newValue) => setEndTime(newValue)}
/>
</LocalizationProvider>
<TagsSelector onChange={setTags} />
<Box
sx={{
display: 'flex',
}}
>
<Typography component="h1" variant="h6">
{' '}
Description
</Typography>
</Box>
<TextField
label="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
multiline
></TextField>

<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Update Post
</Button>
</Box>
</Box>
</Container>
);
}

const checkboxIconBlank = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkboxIconChecked = <CheckBoxIcon fontSize="small" />;
const filter = createFilterOptions();

const GameSelector = ({ onChange }) => {
const [gammeOptions, setGameOptions] = useState([]);
const [value, setValue] = useState('');

useEffect(() => {
GameService.searchGames('').then((games) => setGameOptions(games));
}, []);

const onInputChange = (event, newValue) => {
console.log('newValue');
console.log(newValue);

setValue(newValue);
onChange(newValue);
};

const onFilterOptions = (options, params) => {
const filtered = filter(options, params);

const { inputValue } = params;
// Suggest the creation of a new value
const isExisting = options.some((option) => inputValue === option.name);
if (inputValue !== '' && !isExisting) {
filtered.push({
// inputValue,
id: null,
name: inputValue,
});
}

return filtered;
};

return (
<Autocomplete
clearOnBlur
value={value}
onChange={onInputChange}
options={gammeOptions}
disableCloseOnSelect
filterOptions={onFilterOptions}
getOptionLabel={(o) => (o ? o.name : '')}
isOptionEqualToValue={(option, value) => option.name === value.name}
renderOption={(props, option) => <li {...props}>{option.name}</li>}
renderInput={(params) => (
<TextField
{...params}
label="Game"
required
placeholder="Select or create a game"
/>
)}
/>
);
};

const TagsSelector = ({ onChange }) => {
const [tagOptions, setTagOptions] = useState([]);
const [value, setValue] = useState([]);

useEffect(() => {
TagService.searchTags('').then((tags) => setTagOptions(tags));
}, []);

const onInputChange = (event, newValues) => {
for (const newValue of newValues) {
if (newValue.id === null) {
// if not in options add to options
if (!tagOptions.some((o) => o.name === newValue.name)) {
const newOptions = tagOptions.concat([newValue]);
setTagOptions(newOptions);
}
}
}

setValue(newValues);
onChange(newValues);
};

const onFilterOptions = (options, params) => {
const filtered = filter(options, params);

const { inputValue } = params;
// Suggest the creation of a new value
const isExisting = options.some((option) => inputValue === option.name);
if (inputValue !== '' && !isExisting) {
filtered.push({
// inputValue,
id: null,
name: inputValue,
});
}

return filtered;
};

return (
<Autocomplete
multiple
clearOnBlur
value={value}
onChange={onInputChange}
options={tagOptions}
disableCloseOnSelect
filterOptions={onFilterOptions}
getOptionLabel={(o) => o.name}
isOptionEqualToValue={(option, value) => option.name === value.name}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={checkboxIconBlank}
checkedIcon={checkboxIconChecked}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</li>
)}
renderInput={(params) => (
<TextField {...params} label="Tags" placeholder="" />
)}
/>
);
};
Loading

0 comments on commit f233c96

Please sign in to comment.