Skip to content

Commit

Permalink
add login page and user context
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexTugarev authored and svenefftinge committed Mar 22, 2021
1 parent 525051e commit 188ad83
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 93 deletions.
3 changes: 3 additions & 0 deletions components/dashboard/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<script>
window.PREVIEW_URL = "%REACT_APP_PREVIEW_URL%";
</script>
<title>React App</title>
</head>
<body>
Expand Down
13 changes: 13 additions & 0 deletions components/dashboard/public/login-success/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Login successful</title>
<script>
window.opener.postMessage("auth-success", `https://${window.location.hostname}`);
</script>
</head>
<body>
This browser tab is supposed to close automatically.
</body>
</html>
115 changes: 73 additions & 42 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { Suspense, useContext } from 'react';
import React, { Suspense, useContext, useEffect, useState } from 'react';
import Menu from './components/Menu';
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { Workspaces } from './workspaces/Workspaces';
import { ServiceContext } from './service/service';
import { Login } from './Login';
import { UserContext } from './contexts';
import { gitpodService, service } from './service/service';

const Notifications = React.lazy(() => import('./account/Notifications'));
const Profile = React.lazy(() => import('./account/Profile'));
Expand All @@ -14,51 +15,81 @@ const FeaturePreview = React.lazy(() => import('./settings/FeaturePreview'));
const GitIntegration = React.lazy(() => import('./settings/GitIntegration'));

function App() {
const ctx = useContext(ServiceContext);
if (!ctx.user) {
const { user, setUser } = useContext(UserContext);

const [loading, setLoading] = useState<boolean>(true);
const [userLoadError, setUserLoadError] = useState<string | undefined>(undefined);

useEffect(() => {
(async () => {
try {
const user = await service.getOrLoadUser();
setUser(user);
} catch (error) {
console.log(error);
setUserLoadError(error && error.message);
}
setLoading(false);
})();
}, []);


return (
<Login/>
<BrowserRouter>
<div className="container">
{user && renderMenu()}

{loading && (<h3>Loading...</h3>)}
{userLoadError && (<div><h2>Error</h2><h2>{userLoadError}</h2></div>)}

<Suspense fallback={<h3>Loading...</h3>}>
<Switch>
{user && (
<React.Fragment>
<Route path="/" exact render={
() => <Workspaces gitpodService={gitpodService} />} />
<Route path="/profile" exact component={Profile} />
<Route path="/notifications" exact component={Notifications} />
<Route path="/subscriptions" exact component={Subscriptions} />
<Route path="/env-vars" exact component={EnvVars} />
<Route path="/git-integration" exact component={GitIntegration} />
<Route path="/feature-preview" exact component={FeaturePreview} />
<Route path="/default-ide" exact component={DefaultIDE} />
</React.Fragment>
)}
{!user && (
<React.Fragment>
<Route path="/" exact render={() => <Login />} />
</React.Fragment>
)}
</Switch>
</Suspense>
</div>
</BrowserRouter>
);
}
return (
<BrowserRouter>
<div className="container">
<Menu left={[
{
title: 'Workspaces',
link: '/'
},
{
title: 'Settings',
link: '/profile'
},
]}
right={[
}

const renderMenu = () => (
<Menu left={[
{
title: 'Workspaces',
link: '/'
},
{
title: 'Settings',
link: '/profile'
},
]}
right={[
{
title: 'Docs',
link: 'https://www.gitpod.io/docs/',
title: 'Docs',
link: 'https://www.gitpod.io/docs/',
},
{
title: 'Community',
link: 'https://community.gitpod.io/',
title: 'Community',
link: 'https://community.gitpod.io/',
}
]} />
<Suspense fallback={<div></div>}>
<Switch>
<Route path="/" exact render={
() => <Workspaces gitpodService={ctx.service}/>} />
<Route path="/profile" exact component={Profile} />
<Route path="/notifications" exact component={Notifications} />
<Route path="/subscriptions" exact component={Subscriptions} />
<Route path="/env-vars" exact component={EnvVars} />
<Route path="/git-integration" exact component={GitIntegration} />
<Route path="/feature-preview" exact component={FeaturePreview} />
<Route path="/default-ide" exact component={DefaultIDE} />
</Switch>
</Suspense>
</div>
</BrowserRouter>
);
}
]}
/>)

export default App;
69 changes: 52 additions & 17 deletions components/dashboard/src/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,65 @@
import { AuthProviderInfo } from "@gitpod/gitpod-protocol";
import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";
import { useContext, useState } from "react";
import { useContext, useEffect, useState } from "react";
import Modal from "./components/Modal";
import { ServiceContext } from "./service/service";
import { UserContext } from "./contexts";
import { gitpodHostUrl, reconnect, service } from "./service/service";

export function Login() {
const ctx = useContext(ServiceContext);
const [authProvider, setAuthProvider]= useState([] as AuthProviderInfo[]);
ctx.service.server.getAuthProviders().then(
aps => setAuthProvider(aps)
);
const { setUser } = useContext(UserContext);

const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([]);

useEffect(() => {
(async () => {
setAuthProviders(await service.getAuthProviders());
})();

window.addEventListener("message", (event) => {
// todo: check event.origin

if (event.data === "auth-success") {
if (event.source && "close" in event.source && event.source.close) {
console.log(`try to close window`);
event.source.close();
} else {
// todo: not here, but add a button to the /login-success page to close, if this should not work as expected
}
(async () => {
reconnect();
const user = await service.reloadUser();
setUser(user);
})();
}
})
})

const openLogin = (host: string) => {
const url = getLoginUrl(host);
const newWindow = window.open(url, "gitpod-login");
if (!newWindow) {
console.log(`Failed to open login window for ${host}`);
}
}

return (<div>
<Modal visible={true}>
{authProvider.map(ap => {
return (<a href={getLoginUrl(ap.host)} target="_parent">Login With GitHub</a>);
})}
<div>
<ol>
{authProviders.map(ap => {
return (<li>
<h2><a href="#" onClick={() => openLogin(ap.host)}>Continue with {ap.host}</a></h2>
</li>);
})}
</ol>
</div>
</Modal>
</div>);
}

function getLoginUrl(host: string) {
const returnTo = "https://google.com";
const returnToPart = returnTo ? `&returnTo=${encodeURIComponent(returnTo)}` : '';
const search = `host=${host}${returnToPart}`;
return new GitpodHostUrl(window.location.toString()).withApi({
pathname: '/login/',
search
const returnTo = gitpodHostUrl.with({ pathname: 'login-success'}).toString();
return gitpodHostUrl.withApi({
pathname: '/login',
search: `host=${host}&returnTo=${encodeURIComponent(returnTo)}`
}).toString();
}
12 changes: 7 additions & 5 deletions components/dashboard/src/account/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { User } from "@gitpod/gitpod-protocol";
import { useContext, useState } from "react";
import Modal from "../components/Modal";
import { SettingsPage } from "../components/SettingsPage";
import { ServiceContext } from "../service/service";
import { UserContext } from "../contexts";
import accountMenu from "./account-menu";

export default function Profile() {
const ctx = useContext(ServiceContext);
const { user } = useContext(UserContext);

const [modal, setModal] = useState(false);

const close = () => setModal(false);
return <div>
<Modal visible={modal}>
Expand All @@ -24,18 +26,18 @@ export default function Profile() {
<div className="pb-6">
<div className="pt-6">
<h4>Name</h4>
<input type="text" value={ctx.user!.name} onChange={(v) => { console.log(v) }} />
<input type="text" value={user!.name} onChange={(v) => { console.log(v) }} />
</div>
<div className="pt-6">
<h4>Email</h4>
<input type="text" value={User.getPrimaryEmail(ctx.user!)} onChange={(v) => { console.log(v) }} />
<input type="text" value={User.getPrimaryEmail(user!)} onChange={(v) => { console.log(v) }} />
</div>
</div>
<div className="lg:pl-14">
<div className="pt-6">
<h4>Avatar</h4>
<img className="rounded-full w-24 h-24 border-2 border-transparent hover:border-indigo-400"
src={ctx.user!.avatarUrl} alt={ctx.user!.name} />
src={user!.avatarUrl} alt={user!.name} />
</div>
</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions components/dashboard/src/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useContext } from "react";
import { Link } from "react-router-dom";
import { ServiceContext } from "../service/service";
import { UserContext } from "../contexts";

interface Entry {
title: string, link: string
Expand All @@ -25,7 +25,8 @@ function MenuItem(entry: Entry) {
}

function Menu(props: { left: Entry[], right: Entry[] }) {
const ctx = useContext(ServiceContext);
const { user } = useContext(UserContext);

return (
<header className="lg:px-28 px-10 bg-white flex flex-wrap items-center py-4">
<style dangerouslySetInnerHTML={{
Expand Down Expand Up @@ -58,7 +59,7 @@ function Menu(props: { left: Entry[], right: Entry[] }) {
</nav>
<Link className="lg:ml-4 flex items-center justify-start lg:mb-0 mb-4 pointer-cursor m-l-auto rounded-full border-2 hover:border-gray-400 p-0.5" to="/profile">
<img className="rounded-full w-6 h-6"
src={ctx.user?.avatarUrl || ''} alt={ctx.user?.name || 'Anonymous'} />
src={user?.avatarUrl || ''} alt={user?.name || 'Anonymous'} />
</Link>
</div>
</header>
Expand Down
22 changes: 22 additions & 0 deletions components/dashboard/src/contexts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { User } from '@gitpod/gitpod-protocol';
import React, { createContext, useState } from 'react';

const UserContext = createContext<{
user?: User,
setUser: React.Dispatch<User>
}>({
setUser: () => null
});


const AppProvider: React.FC = ({ children }) => {
const [user, setUser] = useState<User | undefined>(undefined);

return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
)
}

export { UserContext, AppProvider };
22 changes: 11 additions & 11 deletions components/dashboard/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react';
import ReactDOM from 'react-dom';
import "./tailwind.output.css"
import App from './App';
import { ServiceContext, SimpleServiceImpl } from './service/service';
import { AppProvider } from './contexts';
import { service } from './service/service';

import "./tailwind.output.css"

service.getOrLoadUser().then(user => console.log(user.name));

const service = new SimpleServiceImpl();
service.service.server.getLoggedInUser().then(user => {
service.user = user;
ReactDOM.render(
ReactDOM.render(
<React.StrictMode>
<ServiceContext.Provider value={service}>
<App />
</ServiceContext.Provider>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
});
);
Loading

0 comments on commit 188ad83

Please sign in to comment.