Skip to content

Commit 69688f4

Browse files
authored
Merge pull request #25 from oslabs-beta/Ahnafkhvn/shareFunctionality
Ahnafkhvn/share functionality
2 parents 7d52bff + e02c063 commit 69688f4

File tree

8 files changed

+219
-43
lines changed

8 files changed

+219
-43
lines changed

app/src/components/top/NavBar.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { Link } from 'react-router-dom';
33
import Avatar from '@mui/material/Avatar';
44
import Button from '@mui/material/Button';
55
import MoreVertIcon from '@mui/icons-material/MoreVert';
66
import NavBarButtons from './NavBarButtons';
7-
import NavbarDropDown from './NavBarButtons';
87
import NewExportButton from './NewExportButton';
98
import { RootState } from '../../redux/store';
109
import logo from '../../public/icons/win/logo.png';
11-
import { useSelector } from 'react-redux';
10+
import { useSelector, useDispatch } from 'react-redux';
11+
import { publishProject } from '../../helperFunctions/projectGetSaveDel';
12+
import PublishModal from './PublishModal';
13+
1214

1315
const NavBar = () => {
1416
const [dropMenu, setDropMenu] = useState(false);
17+
const state = useSelector((store: RootState) => store.appState);
18+
const [publishModalOpen, setPublishModalOpen] = useState(false);
19+
const [projectName, setProjectName] = useState(state.name || '');
20+
const [invalidProjectName, setInvalidProjectName] = useState(false);
21+
const [invalidProjectNameMessage, setInvalidProjectNameMessage] = useState('');
1522
const isDarkMode = useSelector(
1623
(state: RootState) => state.darkMode.isDarkMode
1724
);
1825

26+
useEffect(()=>{
27+
setProjectName(state.name)
28+
}, [state.name])//update the ProjectName after state.name changes due to loading projects
29+
1930
const buttonContainerStyle = {
2031
display: 'flex',
2132
alignItems: 'center',
@@ -49,6 +60,27 @@ const NavBar = () => {
4960
marginRight: '10px'
5061
};
5162

63+
const handlePublish = () => {
64+
console.log('projectName', projectName)
65+
console.log('state.name', state.name)
66+
if (state.isLoggedIn === true && projectName === '') {
67+
setInvalidProjectName(true);
68+
setPublishModalOpen(true);
69+
return;
70+
}
71+
72+
73+
publishProject(state, projectName)
74+
.then((promise) => {
75+
console.log('Project published successfully', promise);
76+
setPublishModalOpen(false);
77+
})
78+
.catch((error) => {
79+
console.error('Error publishing project:', error.message);
80+
});
81+
82+
};
83+
5284
return (
5385
<nav
5486
className="main-navbar"
@@ -59,15 +91,17 @@ const NavBar = () => {
5991
}
6092
>
6193
<Link to="/" style={{ textDecoration: 'none' }}>
62-
<div className="main-logo">
63-
<Avatar src={logo}></Avatar>
64-
<h1 style={isDarkMode ? { color: 'white' } : { color: 'white' }}>
65-
ReacType
66-
</h1>
67-
</div>
68-
</Link>
94+
<div className="main-logo">
95+
<Avatar src={logo}></Avatar>
96+
<h1 style={isDarkMode ? { color: 'white' } : { color: 'white' }}>
97+
ReacType
98+
</h1>
99+
</div>
100+
</Link>
69101
<div style={buttonContainerStyle}>
70-
<button style={buttonStyle}>Share</button>
102+
<button style={buttonStyle} onClick={handlePublish}>
103+
Publish
104+
</button>
71105
<NewExportButton />
72106
<Button
73107
style={moreVertButtonStyle}
@@ -83,6 +117,15 @@ const NavBar = () => {
83117
style={{ color: 'white' }}
84118
/>
85119
</div>
120+
<PublishModal
121+
open={publishModalOpen}
122+
onClose={() => setPublishModalOpen(false)}
123+
onSave={handlePublish}
124+
projectName={projectName}
125+
onChange={(e) => setProjectName(e.target.value)}
126+
invalidProjectName={invalidProjectName}
127+
invalidProjectNameMessage={invalidProjectNameMessage}
128+
/>
86129
</nav>
87130
);
88131
};

app/src/components/top/NavBarButtons.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useDispatch, useSelector } from 'react-redux';
2-
32
import { Button } from '@mui/material';
43
import DeleteProjects from '../right/DeleteProjects';
54
import ExportButton from '../right/ExportButton';
@@ -11,7 +10,7 @@ import LoginButton from '../right/LoginButton';
1110
import Menu from '@mui/material/Menu';
1211
import MenuItem from '@mui/material/MenuItem';
1312
import ProjectsFolder from '../right/OpenProjects';
14-
import React from 'react';
13+
import React, { useEffect, useRef } from 'react';
1514
import { RootState } from '../../redux/store';
1615
import SaveProjectButton from '../right/SaveProjectButton';
1716
import { allCooperativeState } from '../../redux/reducers/slice/appStateSlice';
@@ -184,6 +183,8 @@ const StyledMenuItem = withStyles((theme) => ({
184183
// where the main function starts //
185184
function navbarDropDown(props) {
186185
const dispatch = useDispatch();
186+
187+
187188
const [modal, setModal] = React.useState(null);
188189
const [anchorEl, setAnchorEl] = React.useState(null);
189190
const [roomCode, setRoomCode] = React.useState('');
@@ -200,6 +201,9 @@ function navbarDropDown(props) {
200201
setAnchorEl(event.currentTarget);
201202
};
202203

204+
205+
206+
203207
const clearWorkspace = () => {
204208
// Reset state for project to initial state
205209
const resetState = () => {
@@ -291,10 +295,35 @@ function navbarDropDown(props) {
291295
</svg>
292296
);
293297

294-
const showMenu = props.dropMenu ? 'navDropDown' : 'hideNavDropDown';
298+
let showMenu = props.dropMenu ? 'navDropDown' : 'hideNavDropDown';
299+
300+
//for closing the menu on clicks outside of it.
301+
const useOutsideClick = (callback) => {
302+
303+
const dropdownRef = useRef(null);
304+
305+
useEffect(() => {
306+
const handleClick = (event) => {
307+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
308+
callback();
309+
}
310+
}
311+
document.addEventListener('click', handleClick, true);
312+
313+
return () => {
314+
document.removeEventListener('click', handleClick, true);//cleanup for memory purposes. ensures handleclick isn't called after the component is no longer rendered
315+
};
316+
}, [dropdownRef]);
317+
318+
return dropdownRef
319+
320+
}
321+
322+
const ref = useOutsideClick(handleClose);
295323

296324
return (
297-
<div className={showMenu}>
325+
// <div ref={dropdownRef} className={showMenu}> dropdownRef making the menu fly off and anchorel messingup
326+
<div ref={ref} className={showMenu}>
298327
<Link to="/tutorial" style={{ textDecoration: 'none' }} target="_blank">
299328
<button>
300329
Tutorial
@@ -398,4 +427,4 @@ function navbarDropDown(props) {
398427
);
399428
}
400429

401-
export default navbarDropDown;
430+
export default navbarDropDown;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import Dialog from '@mui/material/Dialog';
3+
import DialogTitle from '@mui/material/DialogTitle';
4+
import DialogContent from '@mui/material/DialogContent';
5+
import DialogActions from '@mui/material/DialogActions';
6+
import Button from '@mui/material/Button';
7+
import TextField from '@mui/material/TextField';
8+
9+
const PublishModal = ({
10+
open,
11+
onClose,
12+
onSave,
13+
projectName,
14+
onChange,
15+
invalidProjectName,
16+
invalidProjectNameMessage,
17+
}) => {
18+
return (
19+
<Dialog
20+
style={{ color: "#000" }}
21+
open={open}
22+
onClose={onClose}
23+
aria-labelledby="form-dialog-title"
24+
>
25+
<DialogTitle style={{ color: "#000" }} id="form-dialog-title">Publish Project</DialogTitle>
26+
<DialogContent>
27+
<TextField
28+
autoFocus
29+
inputProps={{ style: { color: "black" } }}
30+
margin="dense"
31+
id="name"
32+
label="Project Name"
33+
type="text"
34+
fullWidth
35+
value={projectName}
36+
onChange={onChange}
37+
helperText={invalidProjectNameMessage}
38+
error={invalidProjectName}
39+
/>
40+
</DialogContent>
41+
<DialogActions>
42+
<Button onClick={onClose} color="primary">
43+
Cancel
44+
</Button>
45+
<Button onClick={onSave} color="primary">
46+
Publish
47+
</Button>
48+
</DialogActions>
49+
</Dialog>
50+
);
51+
};
52+
53+
export default PublishModal;

app/src/helperFunctions/projectGetSaveDel.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,42 @@ export const saveProject = (
5858
return project;//returns _id in addition to the project object from the document
5959
};
6060

61+
export const publishProject = (
62+
projectData: State,
63+
projectName: string
64+
): Promise<Object> => {
65+
const body = JSON.stringify({
66+
_id: projectData._id,
67+
project: { ...projectData, name: projectName },
68+
userId: window.localStorage.getItem('ssid'),
69+
username: window.localStorage.getItem('username'),
70+
comments: [],
71+
name: projectName,
72+
});
73+
74+
const response = fetch(`${serverURL}/publishProject`, {
75+
method: 'POST',
76+
headers: {
77+
'Content-Type': 'application/json',
78+
},
79+
credentials: 'include',
80+
body
81+
});
82+
83+
const publishedProject = response
84+
.then((res) => res.json())
85+
.then((data) => {
86+
return {_id: data._id, ...data.project};
87+
})
88+
.catch((err) => {
89+
console.log(`Error publishing project ${err}`);
90+
throw err;
91+
});
92+
93+
return publishedProject;
94+
};
95+
96+
6197
export const deleteProject = (project: any): Promise<Object> => {
6298
const body = JSON.stringify({
6399
name: project.name,

app/src/interfaces/Interfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface State {
44
name: string;
55
_id: string;
66
forked: boolean;
7+
_id: string;
78
isLoggedIn: boolean;
89
components: Component[];
910
rootComponents: number[];
@@ -131,6 +132,12 @@ export interface Attributes {
131132
compLink?: string;
132133
}
133134

135+
// interface PublishResponse {
136+
// success: boolean;
137+
// error?: string;
138+
// }
139+
140+
134141
export interface Arrow {
135142
renderArrow: (id: number) => any;
136143
deleteLines: () => void;

server/controllers/marketplaceController.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Project from '../graphQL/resolvers/query';
22
import { MarketplaceController } from '../interfaces';
33
import { Projects, Users } from '../models/reactypeModels';
4+
import mongoose from 'mongoose';
45

56
// array of objects, objects inside
67
type Projects = { project: {} }[];
@@ -33,36 +34,39 @@ const marketplaceController: MarketplaceController = {
3334
/**
3435
*
3536
* Middleware function that publishes (and saves) a project to the database
36-
* @return sends the updated project to the frontend
37+
* @return sends the updated entire project document to the frontend
3738
*/
38-
publishProject: (req, res, next) => {
39+
publishProject: async (req, res, next) => {
3940
const { _id, project, comments, userId, username, name } = req.body;
4041
const createdAt = Date.now();
42+
console.log('Publish Project', _id, project, comments, userId, username, name )
43+
4144
if (userId === req.cookies.ssid) {
42-
Projects.findOneAndUpdate(
43-
// looks in projects collection for project by Mongo id
44-
{ _id },
45-
// update or insert the project
46-
{ project, createdAt, published: true, comments, name, userId, username },
47-
// Options:
48-
// upsert: true - if none found, inserts new project, otherwise updates it
49-
// new: true - returns updated document not the original one
50-
{ upsert: true, new: true },
51-
(err, result) => {
52-
if (err) {
53-
return next({
54-
log: `Error in marketplaceController.publishProject: ${err}`,
55-
message: {
56-
err: 'Error in marketplaceController.publishProject, check server logs for details'
57-
}
58-
});
59-
}
60-
res.locals.publishedProject = result; //returns the entire document
61-
return next();
62-
}
63-
);
45+
46+
if (mongoose.isValidObjectId(_id)) {
47+
const publishedProject = await Projects.findOneAndUpdate
48+
( // looks in projects collection for project by Mongo id
49+
{ _id },
50+
// update or insert the project
51+
{ project, createdAt, published: true, comments, name, userId, username },
52+
// Options:
53+
// upsert: true - if none found, inserts new project, otherwise updates it
54+
// new: true - returns updated document not the original one
55+
{ upsert: true, new: true }
56+
);
57+
res.locals.publishedProject = publishedProject;
58+
return next();
59+
}else{
60+
const noId = {...project};
61+
delete noId._id; //removing the empty string _id from project
62+
const publishedProject = await Projects.create( { project: noId, createdAt, published: true, comments, name, userId, username });
63+
res.locals.publishedProject = publishedProject.toObject({ minimize: false });
64+
console.log('published backend new', res.locals.publishedProject)
65+
return next();
66+
}
6467
}
6568
else {
69+
console.log('userId did not match')
6670
// we should not expect a user to be able to access another user's id, but included error handling for unexpected errors
6771
return next({
6872
log: 'Error in marketplaceController.publishProject',

0 commit comments

Comments
 (0)