Skip to content

Commit 5fc4940

Browse files
authored
Merge pull request #142 from /issues/141-Save_a_user_s_code_when_they_log_in
Save a user's code when they log in
2 parents eee935a + 758c4c5 commit 5fc4940

9 files changed

Lines changed: 107 additions & 71 deletions

File tree

src/App.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import './App.scss';
33
import Header from './components/Header/Header'
44
import Routes from './components/Routes'
55
import { useCookies } from 'react-cookie';
6+
import { BrowserRouter } from 'react-router-dom';
67

78
function App() {
89
const [cookies] = useCookies(['theme', 'fontSize'])
@@ -11,8 +12,10 @@ function App() {
1112
<div
1213
id='app'
1314
className = {`--${cookies.theme || themeDefault } font-size-${cookies.fontSize || 'small' }`}>
14-
<Header />
15-
<Routes />
15+
<BrowserRouter>
16+
<Header />
17+
<Routes />
18+
</BrowserRouter>
1619
</div>
1720
);
1821
}

src/components/Callback.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import userManager from "../utils/userManager";
77
const Callback = () => {
88
let history = useHistory()
99

10+
const previousRoute = localStorage.getItem('location')
11+
1012
const onSuccess = () => {
11-
history.push("/")
13+
localStorage.removeItem('location')
14+
history.push(previousRoute)
1215
}
1316

1417
const onError = (error) => {
15-
history.push("/");
18+
history.push(previousRoute);
1619
console.error(error);
1720
}
1821

src/components/Editor/Hooks/useProject.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ const pythonCode = {
1616
type: 'python',
1717
components: [
1818
{ extension: 'py', name: 'main',
19-
content: "from emoji import *\nprint('Hello ', world)" },
20-
{ extension: 'py', name: 'emoji',
21-
content: "world = '🌍🌎🌏'\npython = '🐍⌨️'\nsums = '✖️➗➖➕'"},
19+
content: "", index: 0, default: true },
2220
]
2321
}
2422

@@ -43,7 +41,24 @@ export const useProject = (projectType, projectIdentifier = '') => {
4341
})();
4442
}
4543

44+
const cachedProject = JSON.parse(localStorage.getItem('project'))
45+
const loadCachedProject = () => {
46+
dispatch(setProject(cachedProject))
47+
dispatch(setProjectLoaded(true));
48+
localStorage.removeItem('project')
49+
}
50+
4651
useEffect(() => {
52+
var is_cached_saved_project = (projectIdentifier && cachedProject && cachedProject.identifier === projectIdentifier)
53+
var is_cached_unsaved_project = (!projectIdentifier && cachedProject)
54+
if (is_cached_saved_project || is_cached_unsaved_project) {
55+
loadCachedProject()
56+
return
57+
}
58+
else if (cachedProject) {
59+
localStorage.removeItem('project')
60+
}
61+
4762
if (projectIdentifier) {
4863
loadProject();
4964
return;

src/components/Editor/Project/Project.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { setProjectLoaded, setProject } from '../EditorSlice';
1616
import ImageUploadButton from '../ImageUploadButton/ImageUploadButton';
1717
import NewComponentButton from '../NewComponentButton/NewComponentButton';
1818
import RunnerControls from '../../RunButton/RunnerControls';
19-
import { remixProject, updateProject } from '../../../utils/apiCallHandler';
19+
import { saveProject, remixProject, updateProject } from '../../../utils/apiCallHandler';
2020
import ProjectImages from '../ProjectImages/ProjectImages';
2121
import ExternalFiles from '../../ExternalFiles/ExternalFiles';
2222
import ProjectName from './ProjectName.js';
@@ -33,6 +33,11 @@ const Project = () => {
3333

3434
const onClickSave = async () => {
3535
if (!project.identifier) {
36+
const response = await saveProject(project, user.access_token)
37+
const identifier = response.data.identifier;
38+
const project_type = response.data.project_type;
39+
dispatch(setProjectLoaded(false));
40+
history.push(`/${project_type}/${identifier}`)
3641
return;
3742
}
3843

@@ -69,20 +74,20 @@ const Project = () => {
6974
<div className='proj-header'>
7075
<div>
7176
<div>
72-
{user && (project.user_id === user.profile.user) ? (<ProjectName name={project.name} />) : (<h1>{project.name}</h1>)}
77+
{user && (project.user_id === user.profile.user) ? (<ProjectName name={project.name} />) : (<h1>{project.name||"New Project"}</h1>)}
7378
</div>
7479
{ project.parent ? (
7580
<p>Remixed from <a href={host+'/'+project.project_type+'/'+project.parent.identifier}>{project.parent.name}</a></p>
7681
) : null }
7782
</div>
7883
<div className='proj-controls'>
79-
{ project.identifier && (
84+
{
8085
user !== null ? (
8186
<>
82-
{project.user_id === user.profile.user ? (<Button onClickHandler={onClickSave} buttonText="Save Project" />) : (<Button onClickHandler={onClickRemix} buttonText="Remix Project" />)}
87+
{project.user_id === user.profile.user || !project.identifier ? (<Button onClickHandler={onClickSave} buttonText="Save Project" />) : (<Button onClickHandler={onClickRemix} buttonText="Remix Project" />)}
8388
</>
8489
) : null
85-
)}
90+
}
8691
</div>
8792
</div>
8893
) : null }

src/components/Header/Header.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import { render } from "@testing-library/react"
33
import { Provider } from 'react-redux';
44
import configureStore from 'redux-mock-store';
5+
import { MemoryRouter } from "react-router-dom";
56

67
import Header from "./Header";
78

@@ -17,7 +18,7 @@ test("Login button shown when not embedded", () => {
1718
}
1819
}
1920
const store = mockStore(initialState);
20-
const {queryByText} = render(<Provider store={store}><Header /></Provider>)
21+
const {queryByText} = render(<Provider store={store}><MemoryRouter><Header /></MemoryRouter></Provider>)
2122

2223
expect(queryByText(/Login/)).not.toBeNull()
2324
})

src/components/Login/Login.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import React from 'react';
22
import userManager from '../../utils/userManager'
33
import Button from '../../components/Button/Button'
4+
import { useLocation } from 'react-router-dom';
5+
import { useSelector } from 'react-redux';
46

57
const Login = (props) => {
68
const { user } = props;
9+
const location = useLocation()
10+
const project = useSelector((state) => state.editor.project)
711

812
const onLoginButtonClick = (event) => {
913
event.preventDefault();
14+
localStorage.setItem('location', location.pathname)
15+
localStorage.setItem('project', JSON.stringify(project))
1016
userManager.signinRedirect();
1117
}
1218

src/components/Login/Login.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import React from "react";
2+
import store from '../../app/store'
3+
import { MemoryRouter } from "react-router-dom";
24
import { render } from "@testing-library/react"
35
import { Provider } from 'react-redux';
4-
import configureStore from 'redux-mock-store';
56

67
import Login from "./Login";
78

89
test("Login button shown when not logged in", () => {
910
const user = null
10-
const { getByText} = render(<Login user={user} />);
11+
const { getByText} = render(<MemoryRouter initialEntries={['/']}><Provider store={store}><Login user={user} /></Provider></MemoryRouter>);
1112

1213
expect(getByText(/Log/).textContent).toBe("Login")
1314
})
1415

1516
test("Logout button shown when user logged in", () => {
1617
const user = { profile: { email: 'test@example.com' }}
17-
const {getByText} = render(<Login user={user} />)
18+
const {getByText} = render(<MemoryRouter initialEntries={['/']}><Provider store={store}><Login user={user} /></Provider></MemoryRouter>)
1819

1920
expect(getByText(/Log/).textContent).toBe("Logout")
2021
})

src/components/Routes.js

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { BrowserRouter, Route, Switch } from 'react-router-dom'
2+
import { Route, Switch } from 'react-router-dom'
33

44
import ProjectComponentLoader from './Editor/ProjectComponentLoader/ProjectComponentLoader'
55
import NewProject from './NewProject/NewProject'
@@ -10,60 +10,58 @@ import Callback from './Callback'
1010
import SilentRenew from './SilentRenew'
1111

1212
const Routes = () => (
13-
<BrowserRouter>
14-
<Switch>
15-
<Route
16-
exact
17-
path="/auth/callback"
18-
component={Callback}
19-
/>
20-
<Route
21-
exact
22-
path="/auth/silent_renew"
23-
component={SilentRenew}
24-
/>
25-
<Route
26-
exact
27-
path="/"
28-
component={ProjectComponentLoader}
29-
/>
30-
<Route
31-
exact
32-
path="/new"
33-
component={NewProject}
34-
/>
35-
<Route
36-
exact
37-
path="/projects"
38-
component={ProjectIndex}
39-
/>
40-
<Route
41-
exact
42-
path="/:projectType"
43-
component={ProjectComponentLoader}
44-
/>
45-
<Route
46-
exact
47-
path="/:projectType/:identifier"
48-
component={ProjectComponentLoader}
49-
/>
50-
<Route
51-
exact
52-
path="/embedded/:projectType/:identifier"
53-
render={(props) => <ProjectComponentLoader {...props} embedded={true} />}
54-
/>
55-
<Route
56-
exact
57-
path="/python/share/:identifier"
58-
component={ProjectViewer}
59-
/>
60-
<Route
61-
exact
62-
path="/embed/viewer/:identifier"
63-
component={EmbeddedViewer}
64-
/>
65-
</Switch>
66-
</BrowserRouter>
13+
<Switch>
14+
<Route
15+
exact
16+
path="/auth/callback"
17+
component={Callback}
18+
/>
19+
<Route
20+
exact
21+
path="/auth/silent_renew"
22+
component={SilentRenew}
23+
/>
24+
<Route
25+
exact
26+
path="/"
27+
component={ProjectComponentLoader}
28+
/>
29+
<Route
30+
exact
31+
path="/new"
32+
component={NewProject}
33+
/>
34+
<Route
35+
exact
36+
path="/projects"
37+
component={ProjectIndex}
38+
/>
39+
<Route
40+
exact
41+
path="/:projectType"
42+
component={ProjectComponentLoader}
43+
/>
44+
<Route
45+
exact
46+
path="/:projectType/:identifier"
47+
component={ProjectComponentLoader}
48+
/>
49+
<Route
50+
exact
51+
path="/embedded/:projectType/:identifier"
52+
render={(props) => <ProjectComponentLoader {...props} embedded={true} />}
53+
/>
54+
<Route
55+
exact
56+
path="/python/share/:identifier"
57+
component={ProjectViewer}
58+
/>
59+
<Route
60+
exact
61+
path="/embed/viewer/:identifier"
62+
component={EmbeddedViewer}
63+
/>
64+
</Switch>
6765
)
6866

6967
export default Routes

src/utils/apiCallHandler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ export const readProjectList = async (accessToken) => {
5252
return await get(`${host}/api/projects`, headers(accessToken));
5353
}
5454

55+
export const saveProject = async (project, accessToken) => {
56+
return await post(`${host}/api/projects`, {project: project}, headers(accessToken));
57+
}
58+
5559
export const updateProject = async (project, accessToken) => {
5660
return await put(
5761
`${host}/api/projects/${project.identifier}`,

0 commit comments

Comments
 (0)